├── vue.config.js ├── .browserslistrc ├── screenshot.gif ├── jest.config.js ├── public ├── favicon.ico ├── assets │ └── logo.png ├── vue-tour.css ├── index.html ├── css │ └── app.css └── vue-tour.umd.js ├── babel.config.js ├── .editorconfig ├── types ├── vue.d.ts └── index.d.ts ├── .gitignore ├── src ├── main.js ├── shared │ └── constants.js └── components │ ├── VTour.vue │ └── VStep.vue ├── .eslintrc.js ├── tests └── unit │ ├── VStep.spec.js │ └── VTour.spec.js ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── LICENCE ├── vite.config.js ├── package.json ├── dist ├── vue-tour.css ├── v3-tour.es.js └── v3-tour.umd.js ├── .circleci └── config.yml ├── README.md ├── CONTRIBUTING.md └── CHANGELOG.md /vue.config.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sitronik/v3-tour/HEAD/screenshot.gif -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest' 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sitronik/v3-tour/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sitronik/v3-tour/HEAD/public/assets/logo.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /types/vue.d.ts: -------------------------------------------------------------------------------- 1 | import {Tour} from './index'; 2 | 3 | declare module 'vue/types/vue' { 4 | 5 | interface Vue { 6 | $tours: Record; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | yarn.lock 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | 23 | .npmrc 24 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import VTour from './components/VTour.vue' 2 | import VStep from './components/VStep.vue' 3 | 4 | const VueTour = { 5 | install (app, options) { 6 | if (!options) { 7 | options = {} 8 | } 9 | 10 | app.component(VTour.name, VTour) 11 | app.component(VStep.name, VStep) 12 | 13 | app.config.globalProperties.$tours = {} 14 | } 15 | } 16 | 17 | export default VueTour 18 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import './vue'; 2 | import Vue from 'vue'; 3 | 4 | export function install (vue: any): void 5 | 6 | export interface Tour { 7 | // Methods 8 | start(startStep?: string): void 9 | previousStep(): void 10 | nextStep(): void 11 | stop(): void 12 | skip(): void 13 | finish(): void 14 | currentStep: number 15 | 16 | // Computed 17 | isRunning: boolean 18 | isFirst: boolean 19 | isLast: boolean 20 | numberOfSteps: number 21 | } 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | }, 17 | overrides: [ 18 | { 19 | files: [ 20 | '**/__tests__/*.{j,t}s?(x)', 21 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 22 | ], 23 | env: { 24 | jest: true 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tests/unit/VStep.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import VStep from '@/components/VStep.vue' 3 | import { DEFAULT_OPTIONS } from '@/shared/constants' 4 | 5 | describe('VStep.vue', () => { 6 | it('renders props.step.content', () => { 7 | const step = { 8 | target: 'v-step-0', 9 | content: 'This is a demo step!' 10 | } 11 | 12 | const wrapper = shallowMount(VStep, { 13 | propsData: { 14 | step, 15 | stop: () => {}, 16 | labels: DEFAULT_OPTIONS.labels 17 | } 18 | }) 19 | 20 | expect(wrapper.text()).toContain(step.content) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | 34 | You can also use this CodePen as a starter point to reproduce your issue: https://codepen.io/Eligius/pen/wvMXrVY. 35 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 Pulsar 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. 22 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import dynamicImportVars from '@rollup/plugin-dynamic-import-vars' 2 | import vue from '@vitejs/plugin-vue' 3 | import copy from 'rollup-plugin-copy' 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | outDir: 'dist', 9 | publicDir: 'public-vite', 10 | plugins: [vue(), copy({ 11 | targets: [ 12 | { src: 'dist/vue-tour.umd.js', dest: 'public' }, 13 | { src: 'dist/vue-tour.css', dest: 'public' } 14 | ] 15 | })], 16 | build: { 17 | optimizeDeps: { 18 | exclude: ['public'] 19 | }, 20 | lib: { 21 | entry: path.resolve(__dirname, 'src/main.js'), 22 | name: 'VueTour' 23 | }, 24 | rollupOptions: { 25 | plugins: [ 26 | dynamicImportVars() 27 | ], 28 | external: ['vue'], 29 | output: [ 30 | { 31 | assetFileNames: 'vue-tour.css', 32 | format: 'es', 33 | globals: { 34 | vue: 'Vue' 35 | } 36 | }, 37 | { 38 | assetFileNames: 'vue-tour.css', 39 | format: 'umd', 40 | globals: { 41 | vue: 'Vue' 42 | } 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v3-tour", 3 | "version": "3.1.2", 4 | "description": "v3-tour is a lightweight, simple and customizable tour plugin for use with Vue.js. It provides a quick and easy way to guide your users through your application.", 5 | "author": "Sitronik ", 6 | "license": "MIT", 7 | "main": "dist/v3-tour.es.js", 8 | "module": "dist/v3-tour.umd.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sitronik/v3-tour.git" 12 | }, 13 | "typings": "types/index.d.ts", 14 | "keywords": [ 15 | "vue", 16 | "tour" 17 | ], 18 | "homepage": "https://github.com/sitronik/v3-tour.git", 19 | "scripts": { 20 | "serve": "vue-cli-service serve", 21 | "build": "vite build", 22 | "test:unit": "vue-cli-service test:unit", 23 | "lint": "vue-cli-service lint" 24 | }, 25 | "dependencies": { 26 | "@popperjs/core": "^2.11.0", 27 | "hash-sum": "^2.0.0", 28 | "jump.js": "^1.0.2" 29 | }, 30 | "devDependencies": { 31 | "@rollup/plugin-dynamic-import-vars": "^1.1.1", 32 | "@vitejs/plugin-vue": "^1.2.1", 33 | "@vue/cli-plugin-babel": "^5.0.0-alpha.8", 34 | "@vue/cli-plugin-eslint": "^4.5.12", 35 | "@vue/cli-plugin-unit-jest": "^5.0.0-alpha.8", 36 | "@vue/cli-service": "^4.5.11", 37 | "@vue/compiler-sfc": "^3.0.11", 38 | "@vue/eslint-config-standard": "^4.0.0", 39 | "@vue/test-utils": "^2.0.0-rc.4", 40 | "babel-eslint": "^10.1.0", 41 | "core-js": "^3.9.1", 42 | "eslint": "^5.16.0", 43 | "eslint-plugin-vue": "^5.0.0", 44 | "sass": "^1.32.8", 45 | "sass-loader": "^8.0.2", 46 | "vite": "^2.1.5", 47 | "vue": "^3.0.11", 48 | "webpack": "^4.46.0", 49 | "rollup-plugin-copy": "^3.4.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/shared/constants.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_CALLBACKS = { 2 | onStart: () => {}, 3 | onPreviousStep: (currentStep) => {}, 4 | onNextStep: (currentStep) => {}, 5 | onStop: () => {}, 6 | onSkip: () => {}, 7 | onFinish: () => {} 8 | } 9 | 10 | export const DEFAULT_OPTIONS = { 11 | highlight: false, 12 | labels: { 13 | buttonSkip: 'Skip tour', 14 | buttonPrevious: 'Previous', 15 | buttonNext: 'Next', 16 | buttonStop: 'Finish' 17 | }, 18 | enabledButtons: { 19 | buttonSkip: true, 20 | buttonPrevious: true, 21 | buttonNext: true, 22 | buttonStop: true 23 | }, 24 | startTimeout: 0, 25 | stopOnTargetNotFound: true, 26 | useKeyboardNavigation: true, 27 | enabledNavigationKeys: { 28 | escape: true, 29 | arrowRight: true, 30 | arrowLeft: true 31 | }, 32 | debug: false 33 | } 34 | 35 | export const HIGHLIGHT = { 36 | classes: { 37 | active: 'v-tour--active', 38 | targetHighlighted: 'v-tour__target--highlighted', 39 | targetRelative: 'v-tour__target--relative' 40 | }, 41 | transition: 'box-shadow 0s ease-in-out 0s' 42 | } 43 | 44 | export const DEFAULT_STEP_OPTIONS = { 45 | enableScrolling: true, 46 | highlight: DEFAULT_OPTIONS.highlight, // By default use the global tour setting 47 | enabledButtons: DEFAULT_OPTIONS.enabledButtons, 48 | modifiers: [ 49 | { 50 | name: 'arrow', 51 | options: { 52 | element: '.v-step__arrow', 53 | padding: 10 54 | } 55 | }, 56 | { 57 | name: 'preventOverflow', 58 | options: { 59 | rootBoundary: 'window', 60 | padding: 10 61 | } 62 | }, 63 | { 64 | name: 'offset', 65 | options: { 66 | offset: [0, 10] 67 | } 68 | } 69 | ], 70 | placement: 'bottom' 71 | } 72 | 73 | export const KEYS = { 74 | ARROW_RIGHT: 39, 75 | ARROW_LEFT: 37, 76 | ESCAPE: 27 77 | } 78 | -------------------------------------------------------------------------------- /dist/vue-tour.css: -------------------------------------------------------------------------------- 1 | body.v-tour--active{pointer-events:none}.v-tour{pointer-events:auto}.v-tour__target--highlighted{box-shadow:0 0 0 4px rgba(0,0,0,.4);pointer-events:auto;z-index:9999}.v-tour__target--relative{position:relative}.v-step[data-v-4e730e68]{background:#50596c;color:#fff;max-width:320px;border-radius:3px;box-shadow:transparent 0 0 0 0,transparent 0 0 0 0,rgba(0,0,0,.1) 0 4px 6px -1px,rgba(0,0,0,.06) 0 2px 4px -1px;padding:1rem;pointer-events:auto;text-align:center;z-index:10000}.v-step--sticky[data-v-4e730e68]{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%)}.v-step--sticky .v-step__arrow[data-v-4e730e68]{display:none}.v-step__arrow[data-v-4e730e68],.v-step__arrow[data-v-4e730e68]::before{position:absolute;width:10px;height:10px;background:inherit}.v-step__arrow[data-v-4e730e68]{visibility:hidden}.v-step__arrow--dark[data-v-4e730e68]:before{background:#454d5d}.v-step__arrow[data-v-4e730e68]::before{visibility:visible;content:"";transform:rotate(45deg);margin-left:-5px}.v-step[data-popper-placement^=top]>.v-step__arrow[data-v-4e730e68]{bottom:-5px}.v-step[data-popper-placement^=bottom]>.v-step__arrow[data-v-4e730e68]{top:-5px}.v-step[data-popper-placement^=right]>.v-step__arrow[data-v-4e730e68]{left:-5px}.v-step[data-popper-placement^=left]>.v-step__arrow[data-v-4e730e68]{right:-5px}.v-step__header[data-v-4e730e68]{margin:-1rem -1rem .5rem;padding:.5rem;background-color:#454d5d;border-top-left-radius:3px;border-top-right-radius:3px}.v-step__content[data-v-4e730e68]{margin:0 0 1rem 0}.v-step__button[data-v-4e730e68]{background:0 0;border:.05rem solid #fff;border-radius:.1rem;color:#fff;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1rem;outline:0;margin:0 .2rem;padding:.35rem .4rem;text-align:center;text-decoration:none;transition:all .2s ease;vertical-align:middle;white-space:nowrap}.v-step__button[data-v-4e730e68]:hover{background-color:rgba(255,255,255,.95);color:#50596c} -------------------------------------------------------------------------------- /public/vue-tour.css: -------------------------------------------------------------------------------- 1 | body.v-tour--active{pointer-events:none}.v-tour{pointer-events:auto}.v-tour__target--highlighted{box-shadow:0 0 0 4px rgba(0,0,0,.4);pointer-events:auto;z-index:9999}.v-tour__target--relative{position:relative}.v-step[data-v-4e730e68]{background:#50596c;color:#fff;max-width:320px;border-radius:3px;box-shadow:transparent 0 0 0 0,transparent 0 0 0 0,rgba(0,0,0,.1) 0 4px 6px -1px,rgba(0,0,0,.06) 0 2px 4px -1px;padding:1rem;pointer-events:auto;text-align:center;z-index:10000}.v-step--sticky[data-v-4e730e68]{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%)}.v-step--sticky .v-step__arrow[data-v-4e730e68]{display:none}.v-step__arrow[data-v-4e730e68],.v-step__arrow[data-v-4e730e68]::before{position:absolute;width:10px;height:10px;background:inherit}.v-step__arrow[data-v-4e730e68]{visibility:hidden}.v-step__arrow--dark[data-v-4e730e68]:before{background:#454d5d}.v-step__arrow[data-v-4e730e68]::before{visibility:visible;content:"";transform:rotate(45deg);margin-left:-5px}.v-step[data-popper-placement^=top]>.v-step__arrow[data-v-4e730e68]{bottom:-5px}.v-step[data-popper-placement^=bottom]>.v-step__arrow[data-v-4e730e68]{top:-5px}.v-step[data-popper-placement^=right]>.v-step__arrow[data-v-4e730e68]{left:-5px}.v-step[data-popper-placement^=left]>.v-step__arrow[data-v-4e730e68]{right:-5px}.v-step__header[data-v-4e730e68]{margin:-1rem -1rem .5rem;padding:.5rem;background-color:#454d5d;border-top-left-radius:3px;border-top-right-radius:3px}.v-step__content[data-v-4e730e68]{margin:0 0 1rem 0}.v-step__button[data-v-4e730e68]{background:0 0;border:.05rem solid #fff;border-radius:.1rem;color:#fff;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1rem;outline:0;margin:0 .2rem;padding:.35rem .4rem;text-align:center;text-decoration:none;transition:all .2s ease;vertical-align:middle;white-space:nowrap}.v-step__button[data-v-4e730e68]:hover{background-color:rgba(255,255,255,.95);color:#50596c} -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | 7 | defaults: &defaults 8 | docker: 9 | - image: circleci/node:10.16.3 10 | working_directory: ~/vue-tour 11 | 12 | jobs: 13 | build: 14 | <<: *defaults 15 | steps: 16 | - checkout 17 | 18 | # install Cypress dependencies 19 | - run: sudo apt-get update && sudo apt-get install -y libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 xvfb 20 | 21 | # Download and cache dependencies 22 | - restore_cache: 23 | keys: 24 | - v1-dependencies-{{ checksum "package.json" }} 25 | # fallback to using the latest cache if no exact match is found 26 | - v1-dependencies- 27 | 28 | - run: npm ci 29 | 30 | - save_cache: 31 | paths: 32 | - node_modules 33 | key: v1-dependencies-{{ checksum "package.json" }} 34 | 35 | # run unit tests! 36 | - run: npm run test:unit 37 | - run: npm run build 38 | 39 | - run: git worktree add ../vue-tour-landing origin/landing 40 | 41 | # Download and cache dependencies 42 | # - restore_cache: 43 | # keys: 44 | # - v1-dependencies-{{ checksum "../vue-tour-landing/package.json" }} 45 | # # fallback to using the latest cache if no exact match is found 46 | # - v1-dependencies- 47 | 48 | - run: 49 | command: npm ci 50 | working_directory: ~/vue-tour-landing 51 | 52 | # - save_cache: 53 | # paths: 54 | # - ../vue-tour-landing/node_modules 55 | # - /home/circleci/.cache/Cypress 56 | # key: v1-dependencies-{{ checksum "../vue-tour-landing/package.json" }} 57 | 58 | # run e2e tests! 59 | - run: 60 | command: npm run test:e2e -- --headless 61 | working_directory: ~/vue-tour-landing 62 | 63 | - persist_to_workspace: 64 | root: ~/ 65 | paths: 66 | - vue-tour 67 | - vue-tour-landing 68 | 69 | publish: 70 | <<: *defaults 71 | steps: 72 | - attach_workspace: 73 | at: ~/ 74 | 75 | - add_ssh_keys: 76 | fingerprints: 77 | - "eb:09:ae:75:b5:8e:66:63:27:9a:c0:cb:64:6e:79:d5" 78 | 79 | # generate a new landing build and commit it to the gh-pages branch 80 | - run: 81 | command: npm run build 82 | working_directory: ~/vue-tour-landing 83 | 84 | - run: 85 | command: cp -R ./dist ../ 86 | working_directory: ~/vue-tour-landing 87 | 88 | - run: 89 | command: | 90 | ssh-keyscan github.com >> ~/.ssh/known_hosts 91 | git checkout --track origin/gh-pages 92 | rm -rf * 93 | cp -R ../dist/. ./ 94 | git config user.email "mathieumorainville@hotmail.com" 95 | git config user.name "Mathieu Morainville" 96 | git status 97 | git add . 98 | git commit -m "chore(ci): generate a new build" 99 | git push origin gh-pages 100 | working_directory: ~/vue-tour-landing 101 | 102 | # we could use standard-version here to generating a changelog, bump the version with a tag and commit it 103 | 104 | - run: 105 | name: Authenticate with registry 106 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc 107 | when: always 108 | 109 | - run: 110 | name: Publish package 111 | command: cat .npmrc 112 | # command: npm publish 113 | 114 | # use workflows to publish only on tagged commits (see: https://circleci.com/blog/publishing-npm-packages-using-circleci-2-0/) 115 | workflows: 116 | version: 2 117 | build-publish: 118 | jobs: 119 | - build: 120 | filters: 121 | branches: 122 | only: 123 | - master 124 | - staging 125 | - publish: 126 | requires: 127 | - build 128 | filters: 129 | tags: 130 | only: /^v.*/ 131 | branches: 132 | only: 133 | - master 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # v3-tour 2 | 3 | > v3-tour is a lightweight, simple and customizable tour plugin for use with Vue.js. 4 | > It provides a quick and easy way to guide your users through your application. 5 | 6 | [![Vue Tour](./screenshot.gif "Vue Tour")](https://pulsardev.github.io/vue-tour/) 7 | 8 | ## Table of Contents 9 | 10 | - [Getting Started](#getting-started) 11 | - [Something Missing?](#something-missing) 12 | 13 | ## Getting Started 14 | 15 | You can install `v3-tour` using npm or by downloading the minified build on GitHub. 16 | 17 | ``` 18 | npm install v3-tour 19 | ``` 20 | 21 | Then import the plugin in your application entry point (typically main.js if you used vue-cli to scaffold your project) and tell Vue to use it. 22 | Also don't forget to include the styles. You can add the styles provided by default or customize them to your own liking. 23 | 24 | ```javascript 25 | import Vue from 'vue' 26 | import App from './App.vue' 27 | import VueTour from 'v3-tour' 28 | 29 | require('v3-tour/dist/vue-tour.css') 30 | 31 | Vue.use(VueTour) 32 | 33 | new Vue({ 34 | render: h => h(App) 35 | }).$mount('#app') 36 | ``` 37 | 38 | Finally put a `v-tour` component in your template anywhere in your app (usually in App.vue) and pass it an array of steps. 39 | The `target` property of each step can target a DOM element in any component of your app (as long as it exists in the DOM when the concerned step pops up). 40 | 41 | ```html 42 | 51 | 52 | 84 | ``` 85 | 86 | For all individual elements you want to add a step on, make sure it can be retrieved with `document.querySelector()`. You can use any selector, an ID, a CSS class, data attributes, etc. 87 | Once this is done and your steps correctly target some DOM elements of your application, you can start the tour by calling the following method. 88 | 89 | ```javascript 90 | this.$tours['myTour'].start() 91 | ``` 92 | 93 | For a more detailed documentation, checkout the [docs for vue-tour](https://github.com/pulsardev/vue-tour/wiki). 94 | 95 | ## `before()` UI step functions 96 | 97 | If you need to do UI setup work before a step, there's a `before` function you may include in any/each of 98 | your steps. This function will get invoked before the start/next/previous step is rendered. The function must return a promise. The function is invoked when `start`, `nextStep`, and `previousStep` are triggered. When the promise is rejected, it will not move to the next or previous step. If the promise is resolved then it will move in the direction specified. 99 | 100 | It's used when you need to change what's shown on the screen between steps. For example, you may want to hide 101 | one set of menus and open a screen or you want to perform an async operation. This is especially useful in single-page applications. 102 | 103 | ```javascript 104 | steps: [ 105 | { 106 | target: '#v-step-0', // We're using document.querySelector() under the hood 107 | content: `Discover Vue Tour!`, 108 | before: type => new Promise((resolve, reject) => { 109 | // Time-consuming UI/async operation here 110 | resolve('foo') 111 | }) 112 | } 113 | ] 114 | ``` 115 | 116 | ## Something Missing? 117 | 118 | If you have a feature request or found a bug, [let us know](https://github.com/pulsardev/vue-tour/issues) by submitting an issue. 119 | -------------------------------------------------------------------------------- /tests/unit/VTour.spec.js: -------------------------------------------------------------------------------- 1 | import { mount, shallowMount } from '@vue/test-utils' 2 | import VueTour from '@/main' 3 | import VTour from '@/components/VTour.vue' 4 | 5 | const steps = [ 6 | { 7 | target: '#v-step-0', 8 | content: `Discover Vue Tour!` 9 | }, 10 | { 11 | target: '#v-step-1', 12 | content: 'An awesome plugin made with Vue.js!' 13 | } 14 | ] 15 | 16 | describe('VTour.vue', () => { 17 | it('has the correct number of steps', () => { 18 | const wrapper = mount(VTour, { 19 | propsData: { 20 | name: 'myTestTour', 21 | steps 22 | }, 23 | global: { 24 | plugins: [VueTour] 25 | } 26 | }) 27 | 28 | expect(wrapper.vm.steps.length).toEqual(2) 29 | }) 30 | 31 | it('registers itself in the global Vue instance', () => { 32 | const wrapper = mount(VTour, { 33 | propsData: { 34 | name: 'myTestTour', 35 | steps: [] 36 | }, 37 | global: { 38 | plugins: [VueTour] 39 | } 40 | }) 41 | 42 | expect(typeof wrapper.vm.$tours).toBe('object') 43 | expect(wrapper.vm.$tours).toHaveProperty('myTestTour') 44 | }) 45 | 46 | it('stays within the boundaries of the number of steps', async () => { 47 | const wrapper = shallowMount(VTour, { 48 | propsData: { 49 | name: 'myTestTour', 50 | steps 51 | }, 52 | global: { 53 | plugins: [VueTour] 54 | } 55 | }) 56 | 57 | expect(wrapper.vm.currentStep).toEqual(-1) 58 | 59 | await wrapper.vm.start() 60 | expect(wrapper.vm.currentStep).toEqual(0) 61 | 62 | // We call nextStep one more time than needed 63 | for (let i = 0; i < steps.length; i++) { 64 | await wrapper.vm.nextStep() 65 | } 66 | 67 | expect(wrapper.vm.currentStep).toEqual(1) 68 | 69 | // We call previousStep one more time than needed 70 | for (let i = 0; i < steps.length; i++) { 71 | await wrapper.vm.previousStep() 72 | } 73 | 74 | expect(wrapper.vm.currentStep).toEqual(0) 75 | 76 | wrapper.vm.stop() 77 | 78 | expect(wrapper.vm.currentStep).toEqual(-1) 79 | }) 80 | 81 | describe('#before', () => { 82 | let step0 = false 83 | let step1 = false 84 | const beforeSteps = [ 85 | { 86 | target: '#v-step-0', 87 | content: `Discover Vue Tour!`, 88 | before: () => { 89 | step0 = true 90 | return Promise.resolve() 91 | } 92 | }, 93 | { 94 | target: '#v-step-1', 95 | content: 'An awesome plugin made with Vue.js!', 96 | before: () => { 97 | step1 = true 98 | return Promise.resolve() 99 | } 100 | }, 101 | { 102 | target: '#v-step-2', 103 | content: 'An awesome plugin made with Vue.js!', 104 | before: () => { 105 | return Promise.reject(new Error('testing')) 106 | } 107 | }, 108 | { 109 | target: '#v-step-3', 110 | content: 'An awesome plugin made with Vue.js!', 111 | before: () => { 112 | return Promise.resolve() 113 | } 114 | } 115 | ] 116 | 117 | it('invokes before() on start()', async () => { 118 | const wrapper = shallowMount(VTour, { 119 | propsData: { 120 | name: 'myTestTour', 121 | steps: beforeSteps 122 | }, 123 | global: { 124 | plugins: [VueTour] 125 | } 126 | }) 127 | 128 | await wrapper.vm.start() 129 | expect(wrapper.vm.currentStep).toEqual(0) 130 | expect(step0).toEqual(true) 131 | 132 | step0 = false 133 | step1 = false 134 | }) 135 | 136 | it('invokes before() on nextStep()', async () => { 137 | const wrapper = shallowMount(VTour, { 138 | propsData: { 139 | name: 'myTestTour', 140 | steps: beforeSteps 141 | }, 142 | global: { 143 | plugins: [VueTour] 144 | } 145 | }) 146 | 147 | await wrapper.vm.start() 148 | expect(wrapper.vm.currentStep).toEqual(0) 149 | 150 | await wrapper.vm.nextStep() 151 | expect(wrapper.vm.currentStep).toEqual(1) 152 | expect(step1).toEqual(true) 153 | 154 | step0 = false 155 | step1 = false 156 | }) 157 | 158 | it('handles before() promise rejection on start', async () => { 159 | const wrapper = mount(VTour, { 160 | propsData: { 161 | name: 'myTestTour', 162 | steps: beforeSteps 163 | }, 164 | global: { 165 | plugins: [VueTour] 166 | } 167 | }) 168 | 169 | try { 170 | await wrapper.vm.start(2) 171 | expect(true).toEqual(false) // dead code 172 | } catch (e) { 173 | expect(e.message).toEqual('testing') 174 | expect(wrapper.vm.currentStep).toEqual(-1) 175 | } 176 | }) 177 | 178 | it('handles before() promise rejection on nextStep()', async () => { 179 | const wrapper = shallowMount(VTour, { 180 | propsData: { 181 | name: 'myTestTour', 182 | steps: beforeSteps 183 | }, 184 | global: { 185 | plugins: [VueTour] 186 | } 187 | }) 188 | 189 | await wrapper.vm.start(1) 190 | 191 | try { 192 | await wrapper.vm.nextStep() 193 | expect(true).toEqual(false) // dead code 194 | } catch (e) { 195 | expect(e.message).toEqual('testing') 196 | expect(wrapper.vm.currentStep).toEqual(1) 197 | } 198 | }) 199 | 200 | it('handles before() promise rejection on previousStep()', async () => { 201 | const wrapper = shallowMount(VTour, { 202 | propsData: { 203 | name: 'myTestTour', 204 | steps: beforeSteps 205 | }, 206 | global: { 207 | plugins: [VueTour] 208 | } 209 | }) 210 | 211 | await wrapper.vm.start(3) 212 | 213 | try { 214 | await wrapper.vm.previousStep() 215 | expect(true).toEqual(false) // dead code 216 | } catch (e) { 217 | expect(e.message).toEqual('testing') 218 | expect(wrapper.vm.currentStep).toEqual(3) 219 | } 220 | }) 221 | }) 222 | }) 223 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Table of Contents 4 | 5 | - [Developing Vue Tour](#developing-vue-tour) 6 | - [Generating a Changelog](#generating-a-changelog) 7 | - [Git Commit Guidelines](#git-commit-guidelines) 8 | 9 | ## Developing Vue Tour 10 | 11 | Vue Tour is a library which means that in order to be tested it has to be used in a project. 12 | For this you have two options: 13 | - Use `public/index.html` but then you're limited to a built version 14 | - Use the `landing` branch as a git worktree: 15 | ``` 16 | git worktree add ../vue-tour-landing landing 17 | ``` 18 | 19 | ### Pull Requests 20 | 21 | All pull request must target `staging`. 22 | 23 | 24 | ### Merging PRs 25 | 26 | If a PR has a lot of conflicts and you want to make sure it's working or you want to cherry-pick some commits, you can checkout the PR branch locally: 27 | ``` 28 | git fetch origin pull/:ID/head:pr/:ID 29 | ``` 30 | Where `:ID` is the ID of the PR. The previous command will create a new branch `pr/:ID` containing the changes and commits of the PR. 31 | 32 | ### New release 33 | 34 | Go on `staging` branch. 35 | 36 | ``` 37 | git checkout staging 38 | ``` 39 | 40 | Check result of Standard Version. 41 | 42 | ``` 43 | standard-version --dry-run 44 | ``` 45 | 46 | For a better control of the version number, use `--release-as`. corresponds to semver levels: major, minor or patch. 47 | 48 | ``` 49 | standard-version --release-as --dry-run 50 | ``` 51 | 52 | If result is ok, run command without `--dry-run` flag. 53 | 54 | ``` 55 | standard-version --release-as 56 | ``` 57 | 58 | Push version on `staging`. 59 | 60 | ``` 61 | git push --follow-tags origin staging 62 | ``` 63 | 64 | Do a Pull Request from `staging` to `master`. 65 | 66 | Once merged, publish on NPM from `master`. 67 | 68 | ``` 69 | git checkout master 70 | npm publish 71 | ``` 72 | 73 | ## Generating a Changelog 74 | 75 | By using "standard" guidelines we are able to automatically generate a changelog from our git commit messages. 76 | The tool used to do that is [standard-version](https://github.com/conventional-changelog/standard-version). 77 | 78 | Here is an excerpt from their documentation. 79 | 80 | ### First Release 81 | 82 | To generate your changelog for your first release, simply do: 83 | 84 | ```sh 85 | # npm run script 86 | npm run release -- --first-release 87 | # or global bin 88 | standard-version --first-release 89 | ``` 90 | 91 | This will tag a release **without bumping the version in package.json (_et al._)**. 92 | 93 | When ready, push the git tag and `npm publish` your first release. \o/ 94 | 95 | ### Release as a pre-release 96 | 97 | Use the flag `--prerelease` to generate pre-releases: 98 | 99 | Suppose the last version of your code is `1.0.0`, and your code to be committed has patched changes. Run: 100 | 101 | ```bash 102 | # npm run script 103 | npm run release -- --prerelease 104 | ``` 105 | you will get version `1.0.1-0`. 106 | 107 | If you want to name the pre-release, you specify the name via `--prerelease `. 108 | 109 | For example, suppose your pre-release should contain the `alpha` prefix: 110 | 111 | ```bash 112 | # npm run script 113 | npm run release -- --prerelease alpha 114 | ``` 115 | 116 | this will tag the version `1.0.1-alpha.0` 117 | 118 | ### Release as a target type imperatively like `npm version` 119 | 120 | To forgo the automated version bump use `--release-as` with the argument `major`, `minor` or `patch`: 121 | 122 | Suppose the last version of your code is `1.0.0`, you've only landed `fix:` commits, but 123 | you would like your next release to be a `minor`. Simply do: 124 | 125 | ```bash 126 | # npm run script 127 | npm run release -- --release-as minor 128 | # Or 129 | npm run release -- --release-as 1.1.0 130 | ``` 131 | 132 | you will get version `1.1.0` rather than the auto generated version `1.0.1`. 133 | 134 | > **NOTE:** you can combine `--release-as` and `--prerelease` to generate a release. This is useful when publishing experimental feature(s). 135 | 136 | ## Git Commit Guidelines 137 | 138 | We use the Git Commit Guidelines that Google uses for AngularJS to format our git commit messages. Here is an excerpt of those guidelines. 139 | 140 | We have very precise rules over how our git commit messages can be formatted. This leads to **more readable messages** that are easy to follow when looking through the **project history**. 141 | 142 | ### Commit Message Format 143 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 144 | format that includes a **type**, a **scope** and a **subject**: 145 | 146 | ``` 147 | (): 148 | 149 | 150 | 151 |