diff --git a/demo/js/welcome.js b/demo/js/welcome.js index c916685a2..c1b9ea034 100644 --- a/demo/js/welcome.js +++ b/demo/js/welcome.js @@ -23,107 +23,139 @@ } }, classPrefix: prefix, + // This should add the first tour step + steps: [ + { + text: '\n

\n Shepherd is a JavaScript library for guiding users through your app.\n It uses Tippy.js,\n another open source library, to render dialogs for each tour "step".\n

\n \n

\n Among many things, Tippy makes sure your steps never end up off screen or cropped by an overflow.\n (Try resizing your browser to see what we mean.)\n

\n

\n It also offers a robust API for styling animations of steps\n as they enter and exit the view.\n

', + attachTo: { + element: '.hero-welcome', + on: 'bottom' + }, + buttons: [ + { + action() { + return this.cancel(); + }, + secondary: true, + text: 'Exit' + }, + { + action() { + return this.next(); + }, + text: 'Next' + } + ], + id: 'welcome' + } + ], styleVariables: { shepherdThemePrimary: '#00213b', shepherdThemeSecondary: '#e5e5e5' }, useModalOverlay: true }); - shepherd.addStep({ - text: '\n

\n Shepherd is a JavaScript library for guiding users through your app.\n It uses Tippy.js,\n another open source library, to render dialogs for each tour "step".\n

\n \n

\n Among many things, Tippy makes sure your steps never end up off screen or cropped by an overflow.\n (Try resizing your browser to see what we mean.)\n

\n

\n It also offers a robust API for styling animations of steps\n as they enter and exit the view.\n

', - attachTo: { - element: '.hero-welcome', - on: 'bottom' - }, - buttons: [ - { - action: shepherd.cancel, - secondary: true, - text: 'Exit' + + // These steps should be added via `addSteps` + const steps = [ + { + title: 'Including', + text: 'Including Shepherd is easy! Just include shepherd.js. The styles are bundled with the JS.', + attachTo: { + element: '.hero-including', + on: 'bottom' }, - { - action: shepherd.next, - text: 'Next' - } - ], - id: 'welcome' - }); - shepherd.addStep({ - title: 'Including', - text: 'Including Shepherd is easy! Just include shepherd.js. The styles are bundled with the JS.', - attachTo: { - element: '.hero-including', - on: 'bottom' + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: 'Back' + }, + { + action() { + return this.next(); + }, + text: 'Next' + } + ], + id: 'including' }, - buttons: [ - { - action: shepherd.back, - secondary: true, - text: 'Back' + { + title: 'Creating a Shepherd Tour', + text: 'Creating a Shepherd tour is easy. too! ' + 'Just create a \`Tour\` instance, and add as many steps as you want.', + attachTo: { + element: '.hero-example', + on: 'right' }, - { - action: shepherd.next, - text: 'Next' - } - ], - id: 'including' - }); - shepherd.addStep({ - title: 'Creating a Shepherd Tour', - text: 'Creating a Shepherd tour is easy. too! ' + 'Just create a \`Tour\` instance, and add as many steps as you want.', - attachTo: { - element: '.hero-example', - on: 'right' - }, - buttons: [ - { - action: shepherd.back, - secondary: true, - text: 'Back' - }, - { - action: shepherd.next, - text: 'Next' - } - ], - id: 'creating' - }); - shepherd.addStep({ - title: 'Attaching to Elements', - text: 'Your tour steps can target and attach to elements in DOM (like this step).', - attachTo: { - element: '.hero-example', - on: 'left' + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: 'Back' + }, + { + action() { + return this.next(); + }, + text: 'Next' + } + ], + id: 'creating' }, - buttons: [ - { - action: shepherd.back, - secondary: true, - text: 'Back' + { + title: 'Attaching to Elements', + text: 'Your tour steps can target and attach to elements in DOM (like this step).', + attachTo: { + element: '.hero-example', + on: 'left' }, - { - action: shepherd.next, - text: 'Next' - } - ], - id: 'attaching' - }); + buttons: [ + { + action() { + return this.back(); + }, + secondary: true, + text: 'Back' + }, + { + action() { + return this.next(); + }, + text: 'Next' + } + ], + id: 'attaching' + } + ]; + + shepherd.addSteps(steps); + + // This should add steps after the ones added with `addSteps` shepherd.addStep({ title: 'Centered Shepherd Element', text: 'But attachment is totally optional!\n Without a target, a tour step will create an element that\'s centered within the view. Check out the documentation to learn more.', buttons: [ { - action: shepherd.back, + action() { + return this.back(); + }, secondary: true, text: 'Back' }, { - action: shepherd.next, + action() { + return this.next(); + }, text: 'Next' } ], id: 'centered-example' }); + shepherd.addStep({ title: 'Learn more', text: 'Star Shepherd on Github so you remember it for your next project', @@ -133,12 +165,16 @@ }, buttons: [ { - action: shepherd.back, + action() { + return this.back(); + }, secondary: true, text: 'Back' }, { - action: shepherd.next, + action() { + return this.next(); + }, text: 'Done' } ], diff --git a/docs-src/tutorials/02-usage.md b/docs-src/tutorials/02-usage.md index 9f3bda783..cc44f97bb 100644 --- a/docs-src/tutorials/02-usage.md +++ b/docs-src/tutorials/02-usage.md @@ -108,7 +108,7 @@ const myTour = new Shepherd.Tour(options); - `confirmCancelMessage`: The message to display in the confirm dialog - `defaultStepOptions`: Default options for Steps created through `addStep` - `modalContainer` An optional container element for the modal. If not set, the modal will be appended to `document.body`. -- `steps`: An array of Step instances to initialize the tour with. +- `steps`: An array of step options objects or Step instances to initialize the tour with. - `tourName`: An optional "name" for the tour. This will be appended to the the tour's dynamically generated `id` property -- which is also set on the `body` element as the `data-shepherd-active-tour` attribute whenever the tour becomes active. - `useModalOverlay`: Whether or not steps should be placed above a darkened modal overlay. If true, the overlay will create an opening around the target element so that it can remain interactive. @@ -182,6 +182,13 @@ the step will execute. For example: - `classes`: Extra classes to apply to the `` - `secondary`: A boolean, that when true, adds a `shepherd-button-secondary` class to the button - `action`: A function executed when the button is clicked on + It is automatically bound to the `tour` the step is associated with, so things like `this.next` will + work inside the action. You can use action to skip steps or navigate to specific steps, with something like: + ```javascript + action() { + return this.show('some_step_name'); + } + ``` - `events`: A hash of events to bind onto the button, for example `{'mouseover': function(){}}`. Adding a click event to `events` when you already have an `action` specified is not supported. You can use `events` to skip steps or navigate to specific steps, with something like: diff --git a/src/js/components/shepherd-content/shepherd-footer/index.jsx b/src/js/components/shepherd-content/shepherd-footer/index.jsx index 6b881a825..a8a83cb87 100644 --- a/src/js/components/shepherd-content/shepherd-footer/index.jsx +++ b/src/js/components/shepherd-content/shepherd-footer/index.jsx @@ -9,14 +9,19 @@ export default class ShepherdFooter extends Component { const { buttons } = step.options; return ; } - _addButtons(buttons, classPrefix, styles) { + _addButtons(buttons, classPrefix, step, styles) { if (buttons) { return buttons.map((config) => { - return ; + return ; }); } diff --git a/src/js/components/shepherd-content/shepherd-footer/shepherd-button/index.jsx b/src/js/components/shepherd-content/shepherd-footer/shepherd-button/index.jsx index 329f31308..96e844168 100644 --- a/src/js/components/shepherd-content/shepherd-footer/shepherd-button/index.jsx +++ b/src/js/components/shepherd-content/shepherd-footer/shepherd-button/index.jsx @@ -4,12 +4,12 @@ const { Component } = preact; export default class ShepherdButton extends Component { render(props) { - const { classPrefix, config, styles } = props; + const { classPrefix, config, step, styles } = props; const { action, classes, secondary, text } = config; return ; diff --git a/src/js/step.jsx b/src/js/step.jsx index 315563dad..2b95145e6 100644 --- a/src/js/step.jsx +++ b/src/js/step.jsx @@ -68,11 +68,13 @@ export class Step extends Evented { * When the promise resolves, the rest of the `show` code for the step will execute. * @param {Object[]} options.buttons An array of buttons to add to the step. These will be rendered in a * footer below the main body text. - * @param {function} options.buttons.button.action A function executed when the button is clicked on + * @param {function} options.buttons.button.action A function executed when the button is clicked on. + * It is automatically bound to the `tour` the step is associated with, so things like `this.next` will + * work inside the action. * You can use action to skip steps or navigate to specific steps, with something like: * ```js - * action: function() { - * return Shepherd.activeTour.show('some_step_name'); + * action() { + * return this.show('some_step_name'); * } * ``` * @param {string} options.buttons.button.classes Extra classes to apply to the `` diff --git a/src/js/tour.jsx b/src/js/tour.jsx index 85b1c65d1..a211c925d 100644 --- a/src/js/tour.jsx +++ b/src/js/tour.jsx @@ -44,7 +44,7 @@ export class Tour extends Evented { * mousewheel, arrow keys, etc. You may want to use this to ensure you are driving the scroll position with the tour. * @param {HTMLElement} options.modalContainer An optional container element for the modal. * If not set, the modal will be appended to `document.body`. - * @param {Step[]} options.steps An array of Step instances to initialize the tour with + * @param {object[] | Step[]} options.steps An array of step options objects or Step instances to initialize the tour with * @param {object} options.styleVariables An object hash of style variables to override * @param {string} options.tourName An optional "name" for the tour. This will be appended to the the tour's * dynamically generated `id` property -- which is also set on the `body` element as the `data-shepherd-active-tour` attribute @@ -62,7 +62,8 @@ export class Tour extends Evented { this.options = options; this.classPrefix = this.options ? normalizePrefix(this.options.classPrefix) : ''; this.styles = generateStyles(options); - this.steps = this.options.steps || []; + this.steps = []; + this.addSteps(this.options.steps); // Pass these events onto the global Shepherd object const events = ['active', 'cancel', 'complete', 'inactive', 'show', 'start']; @@ -111,6 +112,20 @@ export class Tour extends Evented { return step; } + /** + * Add multiple steps to the tour + * @param {Array | Array} steps The steps to add to the tour + */ + addSteps(steps) { + if (Array.isArray(steps)) { + steps.forEach((step) => { + this.addStep(step); + }); + } + + return this; + } + /** * Go to the previous step in the tour */ diff --git a/src/types/step.d.ts b/src/types/step.d.ts index 08c89ae54..023b5e6fa 100644 --- a/src/types/step.d.ts +++ b/src/types/step.d.ts @@ -177,6 +177,14 @@ declare namespace Step { interface StepOptionsButton { /** * A function executed when the button is clicked on + * It is automatically bound to the `tour` the step is associated with, so things like `this.next` will + * work inside the action. + * You can use action to skip steps or navigate to specific steps, with something like: + * ```js + * action() { + * return this.show('some_step_name'); + * } + * ``` */ action?: (() => void); diff --git a/src/types/tour.d.ts b/src/types/tour.d.ts index 4a2745523..eb3e76989 100644 --- a/src/types/tour.d.ts +++ b/src/types/tour.d.ts @@ -19,6 +19,11 @@ declare class Tour extends Evented { */ addStep(options: Step | Step.StepOptions): Step; + /** + * Add multiple steps to the tour + */ + addSteps(steps: Array | Array): Tour; + /** * Go to the previous step in the tour */ @@ -103,9 +108,9 @@ declare namespace Tour { defaultStepOptions?: Step.StepOptions; /** - * An array of Step instances to initialize the tour with + * An array of step options objects or Step instances to initialize the tour with */ - steps?: Step[]; + steps?: Array | Array; /** * An object hash of style variables to override diff --git a/test/cypress/integration/modal.spec.js b/test/cypress/integration/modal.spec.js index 12c42977c..3da026764 100644 --- a/test/cypress/integration/modal.spec.js +++ b/test/cypress/integration/modal.spec.js @@ -73,15 +73,13 @@ describe('Modal mode', () => { const steps = () => { return [ { + attachTo: { + element: '.hero-welcome', + on: 'bottom' + }, + highlightClass: 'highlight', id: 'test-highlight', - options: { - attachTo: { - element:'.hero-welcome', - on:'bottom' - }, - highlightClass: 'highlight', - text: 'Testing highlight' - } + text: 'Testing highlight' } ]; }; diff --git a/test/cypress/integration/test.acceptance.js b/test/cypress/integration/test.acceptance.js index 7ffddc913..cb436ebb0 100644 --- a/test/cypress/integration/test.acceptance.js +++ b/test/cypress/integration/test.acceptance.js @@ -24,15 +24,13 @@ describe('Shepherd Acceptance Tests', () => { const steps = () => { return [ { - id: 'welcome', - options: { - text: 'Shepherd is a JavaScript library', - attachTo: { - element: '.hero-welcome', - on: 'bottom' - }, - classes: 'shepherd shepherd-transparent-text' - } + text: 'Shepherd is a JavaScript library', + attachTo: { + element: '.hero-welcome', + on: 'bottom' + }, + classes: 'shepherd shepherd-transparent-text', + id: 'welcome' } ]; }; @@ -59,15 +57,13 @@ describe('Shepherd Acceptance Tests', () => { const steps = () => { return [ { - id: 'including', - options: { - title: 'Including', - text: 'Including Shepherd is easy!', - attachTo: { - element: heroIncludingElement, - on: 'bottom' - } - } + title: 'Including', + text: 'Including Shepherd is easy!', + attachTo: { + element: heroIncludingElement, + on: 'bottom' + }, + id: 'including' } ]; }; @@ -87,10 +83,8 @@ describe('Shepherd Acceptance Tests', () => { return [ { id: 'undefined-attachto', - options: { - title: 'Undefined attachTo', - text: 'When attachTo is undefined, the step is centered.' - } + title: 'Undefined attachTo', + text: 'When attachTo is undefined, the step is centered.' } ]; }; diff --git a/test/cypress/utils/default-steps.js b/test/cypress/utils/default-steps.js index 2fc2dd262..4941e8f2d 100644 --- a/test/cypress/utils/default-steps.js +++ b/test/cypress/utils/default-steps.js @@ -1,8 +1,7 @@ export default function(shepherd) { return [ { - options: { - text: ` + text: `

Shepherd is a JavaScript library for guiding users through your app. It uses Tippy.js, @@ -17,93 +16,86 @@ export default function(shepherd) { It also offers a robust API for styling animations of steps as they enter and exit the view.

`, - attachTo: { - element: '.hero-welcome', - on: 'bottom' - }, - classes: 'shepherd-step-element shepherd-transparent-text first-step', - buttons: [ - { - action: shepherd.cancel, - classes: 'shepherd-button-secondary', - text: 'Exit' - }, { - action: shepherd.next, - classes: 'shepherd-button-example-primary', - text: 'Next' - } - ], - id: 'welcome' - } + attachTo: { + element: '.hero-welcome', + on: 'bottom' + }, + classes: 'shepherd-step-element shepherd-transparent-text first-step', + buttons: [ + { + action: shepherd.cancel, + classes: 'shepherd-button-secondary', + text: 'Exit' + }, { + action: shepherd.next, + classes: 'shepherd-button-example-primary', + text: 'Next' + } + ], + id: 'welcome' }, { - options: { - title: 'Including', - text: 'Including Shepherd is easy! Just include shepherd.js. The styles are bundled with the JS.', - attachTo: { - element: '.hero-including', - on: 'bottom' - }, - buttons: [ - { - action: shepherd.back, - classes: 'shepherd-button-secondary', - text: 'Back' - }, { - action: shepherd.next, - classes: 'shepherd-button-example-primary', - text: 'Next' - } - ], - id: 'including', - classes: 'shepherd-step-element second-step' - } + title: 'Including', + text: 'Including Shepherd is easy! Just include shepherd.js. The styles are bundled with the JS.', + attachTo: { + element: '.hero-including', + on: 'bottom' + }, + buttons: [ + { + action: shepherd.back, + classes: 'shepherd-button-secondary', + text: 'Back' + }, { + action: shepherd.next, + classes: 'shepherd-button-example-primary', + text: 'Next' + } + ], + id: 'including', + classes: 'shepherd-step-element second-step' }, { - options: { - title: 'Example Shepherd', - text: 'Creating a Shepherd is easy too! Just create Shepherd and add as many steps as you want. Check out the documentation to learn more.', - attachTo: { - element: '.hero-example', - on: 'bottom' - }, - buttons: [ - { - action: shepherd.back, - classes: 'shepherd-button-secondary', - text: 'Back' - }, { - action: shepherd.next, - classes: 'shepherd-button-example-primary', - text: 'Next' - } - ], - id: 'example', - classes: 'shepherd-step-element third-step' - } + title: 'Example Shepherd', + text: 'Creating a Shepherd is easy too! Just create Shepherd and add as many steps as you want. Check out the documentation to learn more.', + attachTo: { + element: '.hero-example', + on: 'bottom' + }, + buttons: [ + { + action: shepherd.back, + classes: 'shepherd-button-secondary', + text: 'Back' + }, { + action: shepherd.next, + classes: 'shepherd-button-example-primary', + text: 'Next' + } + ], + id: 'example', + classes: 'shepherd-step-element third-step' }, { - options: { - title: 'Learn more', - text: 'Star Shepherd on Github so you remember it for your next project', - attachTo: { - element:'.hero-followup', - on: 'left' - }, - buttons: [ - { - action: shepherd.back, - classes: 'shepherd-button-secondary', - text: 'Back' - }, { - action: shepherd.next, - classes: 'shepherd-button-example-primary', - text: 'Done' - } - ], - id: 'followup', - classes: 'shepherd-step-element fourth-step' - } + title: 'Learn more', + text: 'Star Shepherd on Github so you remember it for your next project', + attachTo: { + element: '.hero-followup', + on: 'left' + }, + buttons: [ + { + action: shepherd.back, + classes: 'shepherd-button-secondary', + text: 'Back' + }, { + action: shepherd.next, + classes: 'shepherd-button-example-primary', + text: 'Done' + } + ], + id: 'followup', + classes: 'shepherd-step-element fourth-step' } ]; } diff --git a/test/cypress/utils/setup-tour.js b/test/cypress/utils/setup-tour.js index fca324d64..bf18903b1 100644 --- a/test/cypress/utils/setup-tour.js +++ b/test/cypress/utils/setup-tour.js @@ -20,10 +20,7 @@ export default function(Shepherd, globalDefaults, customSteps, otherOptions) { const steps = typeof customSteps === 'function' ? customSteps(shepherd) : defaultSteps(shepherd); - steps.forEach((step) => { - const { options } = step; - shepherd.addStep(options); - }); + shepherd.addSteps(steps); return shepherd; } diff --git a/yarn.lock b/yarn.lock index df5ebeba4..b4555eeee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5181,7 +5181,7 @@ lodash@4.17.11: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.2.0: +lodash@4.17.15, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.2.0: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -5225,14 +5225,7 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= -magic-string@0.25.2: - version "0.25.2" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9" - integrity sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg== - dependencies: - sourcemap-codec "^1.4.4" - -magic-string@^0.25.2: +magic-string@0.25.3, magic-string@^0.25.2: version "0.25.3" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA== @@ -6933,14 +6926,14 @@ rollup-plugin-filesize@^6.1.1: gzip-size "^5.1.1" terser "^4.0.0" -rollup-plugin-license@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-license/-/rollup-plugin-license-0.9.0.tgz#58660e424fe80223b5398cc06d22b95342508ad3" - integrity sha512-/1PW3C1+xIeUsaSQdvRsMFu7Oqmou6ArLBy5vDfem1Yv8wEBGlJ4+izYTZn61Lq39srT+6bndK+zfYTz0CrXQQ== +rollup-plugin-license@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-license/-/rollup-plugin-license-0.10.0.tgz#2060e31938cfbb3ce7db0a74a4dee57946d5a7fb" + integrity sha512-DjoT3eQbTfTbRn1iobWjXZjDGpSGLQRXUNgxiIg5HZ4WvH4Z9eX4dR40k/Y2uHO/MJAebYh0QaZiyqHQyYHSJg== dependencies: commenting "1.1.0" - lodash "4.17.11" - magic-string "0.25.2" + lodash "4.17.15" + magic-string "0.25.3" mkdirp "0.5.1" moment "2.24.0"