kamrify commited on
Commit
6aa2828
·
1 Parent(s): 28ba7df

Add tour functionality

Browse files
Files changed (5) hide show
  1. index.html +87 -22
  2. src/config.ts +5 -0
  3. src/driver.ts +48 -2
  4. src/popover.ts +27 -18
  5. src/style.css +5 -0
index.html CHANGED
@@ -183,6 +183,7 @@
183
  <button id="next-prev-button">Next Previous Buttons</button>
184
  <button id="close-button">Close Buttons</button>
185
  <button id="button-texts">Button Texts</button>
 
186
  <button id="button-config-events">Button Listeners</button>
187
  </div>
188
 
@@ -193,6 +194,12 @@
193
  <button id="popover-hook">Popover Hook</button>
194
  </div>
195
 
 
 
 
 
 
 
196
  <ul>
197
  <li>Written in TypeScript</li>
198
  <li>Lightweight — only 5kb gzipped</li>
@@ -324,6 +331,51 @@ npm install driver.js</pre
324
  <script type="module">
325
  import { driver } from "./src/driver.ts";
326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  document.getElementById("no-buttons").addEventListener("click", () => {
328
  const driverObj = driver({});
329
 
@@ -426,37 +478,50 @@ npm install driver.js</pre
426
 
427
  document.getElementById("popover-hook").addEventListener("click", () => {
428
  const driverObj = driver({
429
- onPopoverRendered: (popover) => {
430
- popover.title.innerText = 'Modified Parent';
431
- }
432
  });
433
  driverObj.highlight({
434
- element: '.page-header',
435
  popover: {
436
- title: 'Page Title',
437
- description: 'Body of the popover',
438
- side: 'bottom',
439
- align: 'start',
440
- onPopoverRendered: (popover) => {
441
- popover.title.innerText = 'Modified';
442
- }
443
- }
444
- })
445
  });
446
 
447
  document.getElementById("custom-classes").addEventListener("click", () => {
448
  const driverObj = driver({
449
- popoverClass: "custom-driver-popover"
450
- })
451
 
452
  driverObj.highlight({
453
  popover: {
454
  popoverClass: "custom-driver-popover",
455
  title: "Custom Classes",
456
  description: "Popover and buttons have custom classes",
457
- showButtons: ["close", "next", "previous"]
458
- }
459
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  });
461
 
462
  document.getElementById("button-config-events").addEventListener("click", () => {
@@ -623,19 +688,19 @@ npm install driver.js</pre
623
  const driverObj = driver({
624
  animate: true,
625
  onDeselected: (element, step) => {
626
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
627
  console.log(`Deselected: ${elementText}\n${JSON.stringify(step)}`);
628
  },
629
  onHighlightStarted: (element, step) => {
630
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
631
  console.log(`Highlight Started: ${elementText}\n${JSON.stringify(step)}`);
632
  },
633
  onHighlighted: (element, step) => {
634
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
635
  console.log(`Highlighted: ${elementText}\n${JSON.stringify(step)}`);
636
  },
637
  onDestroyed: (element, step) => {
638
- const elementText = element?.textContent?.slice(0, 10) || ' - N/A -';
639
  console.log(`Destroyed: ${elementText}\n${JSON.stringify(step)}`);
640
  },
641
  });
 
183
  <button id="next-prev-button">Next Previous Buttons</button>
184
  <button id="close-button">Close Buttons</button>
185
  <button id="button-texts">Button Texts</button>
186
+ <button id="disabled-buttons">Disabled Buttons</button>
187
  <button id="button-config-events">Button Listeners</button>
188
  </div>
189
 
 
194
  <button id="popover-hook">Popover Hook</button>
195
  </div>
196
 
197
+ <h2>Tour Feature</h2>
198
+ <p>Examples below show the tour usage of driver.js.</p>
199
+ <div class="buttons">
200
+ <button id="basic-tour">Basic Tour</button>
201
+ </div>
202
+
203
  <ul>
204
  <li>Written in TypeScript</li>
205
  <li>Lightweight — only 5kb gzipped</li>
 
331
  <script type="module">
332
  import { driver } from "./src/driver.ts";
333
 
334
+ document.getElementById("basic-tour").addEventListener("click", () => {
335
+ const driverObj = driver({
336
+ steps: [
337
+ {
338
+ element: ".page-header",
339
+ popover: {
340
+ title: "New and Improved Driver.js",
341
+ description:
342
+ "Driver.js has been written from the ground up. The new version is more powerful, has less surprises, more customizable and tons of new features.",
343
+ side: "bottom",
344
+ align: "start",
345
+ },
346
+ },
347
+ {
348
+ element: ".page-header h1",
349
+ popover: {
350
+ title: "No Stacking Issues",
351
+ description:
352
+ "Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
353
+ side: "left",
354
+ align: "start",
355
+ },
356
+ },
357
+ {
358
+ element: ".page-header sup",
359
+ popover: {
360
+ title: "Improved Hooks",
361
+ description:
362
+ "Unlike the older version, new version doesn't work with z-indexes so no more positional issues.",
363
+ side: "bottom",
364
+ align: "start",
365
+ },
366
+ },
367
+ {
368
+ popover: {
369
+ title: "No Element",
370
+ description: "You can now have popovers without elements as well",
371
+ },
372
+ },
373
+ ],
374
+ });
375
+
376
+ driverObj.drive();
377
+ });
378
+
379
  document.getElementById("no-buttons").addEventListener("click", () => {
380
  const driverObj = driver({});
381
 
 
478
 
479
  document.getElementById("popover-hook").addEventListener("click", () => {
480
  const driverObj = driver({
481
+ onPopoverRendered: popover => {
482
+ popover.title.innerText = "Modified Parent";
483
+ },
484
  });
485
  driverObj.highlight({
486
+ element: ".page-header",
487
  popover: {
488
+ title: "Page Title",
489
+ description: "Body of the popover",
490
+ side: "bottom",
491
+ align: "start",
492
+ onPopoverRendered: popover => {
493
+ popover.title.innerText = "Modified";
494
+ },
495
+ },
496
+ });
497
  });
498
 
499
  document.getElementById("custom-classes").addEventListener("click", () => {
500
  const driverObj = driver({
501
+ popoverClass: "custom-driver-popover",
502
+ });
503
 
504
  driverObj.highlight({
505
  popover: {
506
  popoverClass: "custom-driver-popover",
507
  title: "Custom Classes",
508
  description: "Popover and buttons have custom classes",
509
+ showButtons: ["close", "next", "previous"],
510
+ },
511
+ });
512
+ });
513
+
514
+ document.getElementById("disabled-buttons").addEventListener("click", () => {
515
+ const driverObj = driver();
516
+ driverObj.highlight({
517
+ element: "#disabled-buttons",
518
+ popover: {
519
+ title: "Disable Buttons",
520
+ description: "You can selectively disable buttons as well",
521
+ showButtons: ["next", "previous", "close"],
522
+ disableButtons: ["next", "previous"],
523
+ },
524
+ });
525
  });
526
 
527
  document.getElementById("button-config-events").addEventListener("click", () => {
 
688
  const driverObj = driver({
689
  animate: true,
690
  onDeselected: (element, step) => {
691
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
692
  console.log(`Deselected: ${elementText}\n${JSON.stringify(step)}`);
693
  },
694
  onHighlightStarted: (element, step) => {
695
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
696
  console.log(`Highlight Started: ${elementText}\n${JSON.stringify(step)}`);
697
  },
698
  onHighlighted: (element, step) => {
699
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
700
  console.log(`Highlighted: ${elementText}\n${JSON.stringify(step)}`);
701
  },
702
  onDestroyed: (element, step) => {
703
+ const elementText = element?.textContent?.slice(0, 10) || " - N/A -";
704
  console.log(`Destroyed: ${elementText}\n${JSON.stringify(step)}`);
705
  },
706
  });
src/config.ts CHANGED
@@ -2,6 +2,8 @@ import { DriveStep } from "./driver";
2
  import {AllowedButtons, PopoverDOM} from "./popover";
3
 
4
  export type Config = {
 
 
5
  animate?: boolean;
6
  backdropColor?: string;
7
  smoothScroll?: boolean;
@@ -15,11 +17,13 @@ export type Config = {
15
  popoverClass?: string;
16
  popoverOffset?: number;
17
  showButtons?: AllowedButtons[];
 
18
 
19
  // Button texts
20
  nextBtnText?: string;
21
  prevBtnText?: string;
22
  closeBtnText?: string;
 
23
 
24
  // Called after the popover is rendered
25
  onPopoverRendered?: (popover: PopoverDOM) => void;
@@ -49,6 +53,7 @@ export function configure(config: Config = {}) {
49
  stageRadius: 5,
50
  popoverOffset: 10,
51
  showButtons: ["next", "previous", "close"],
 
52
  backdropColor: "#000",
53
  ...config,
54
  };
 
2
  import {AllowedButtons, PopoverDOM} from "./popover";
3
 
4
  export type Config = {
5
+ steps?: DriveStep[];
6
+
7
  animate?: boolean;
8
  backdropColor?: string;
9
  smoothScroll?: boolean;
 
17
  popoverClass?: string;
18
  popoverOffset?: number;
19
  showButtons?: AllowedButtons[];
20
+ disableButtons?: AllowedButtons[];
21
 
22
  // Button texts
23
  nextBtnText?: string;
24
  prevBtnText?: string;
25
  closeBtnText?: string;
26
+ doneBtnText?: string;
27
 
28
  // Called after the popover is rendered
29
  onPopoverRendered?: (popover: PopoverDOM) => void;
 
53
  stageRadius: 5,
54
  popoverOffset: 10,
55
  showButtons: ["next", "previous", "close"],
56
+ disableButtons: [],
57
  backdropColor: "#000",
58
  ...config,
59
  };
src/driver.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { destroyPopover, Popover } from "./popover";
2
  import { destroyStage } from "./stage";
3
  import { destroyEvents, initEvents, requireRefresh } from "./events";
4
  import { Config, configure, getConfig } from "./config";
@@ -38,6 +38,49 @@ export function driver(options: Config = {}) {
38
  listen("escapePress", handleClose);
39
  }
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  function destroy() {
42
  const activeElement = getState("activeElement");
43
  const activeStep = getState("activeStep");
@@ -72,7 +115,10 @@ export function driver(options: Config = {}) {
72
  refresh: () => {
73
  requireRefresh();
74
  },
75
- drive: (steps: DriveStep[]) => console.log(steps),
 
 
 
76
  highlight: (step: DriveStep) => {
77
  init();
78
  highlight({
 
1
+ import { AllowedButtons, destroyPopover, Popover } from "./popover";
2
  import { destroyStage } from "./stage";
3
  import { destroyEvents, initEvents, requireRefresh } from "./events";
4
  import { Config, configure, getConfig } from "./config";
 
38
  listen("escapePress", handleClose);
39
  }
40
 
41
+ function drive(stepIndex: number = 0) {
42
+ const steps = getConfig("steps");
43
+ if (!steps) {
44
+ console.error("No steps to drive through");
45
+ destroy();
46
+ return;
47
+ }
48
+
49
+ if (!steps[stepIndex]) {
50
+ console.warn(`Step not found at index: ${stepIndex}`);
51
+ destroy();
52
+ }
53
+
54
+ const currentStep = steps[stepIndex];
55
+ const hasNextStep = steps[stepIndex + 1];
56
+ const hasPreviousStep = steps[stepIndex - 1];
57
+
58
+ const doneBtnText = currentStep.popover?.doneBtnText || getConfig("doneBtnText") || "Done";
59
+
60
+ highlight({
61
+ ...currentStep,
62
+ popover: {
63
+ showButtons: ["next", "previous", "close"],
64
+ nextBtnText: !hasNextStep ? doneBtnText : undefined,
65
+ disableButtons: [...(!hasPreviousStep ? ["previous" as AllowedButtons] : [])],
66
+ onNextClick: () => {
67
+ if (!hasNextStep) {
68
+ destroy();
69
+ } else {
70
+ drive(stepIndex + 1);
71
+ }
72
+ },
73
+ onPrevClick: () => {
74
+ drive(stepIndex - 1);
75
+ },
76
+ onCloseClick: () => {
77
+ destroy();
78
+ },
79
+ ...(currentStep?.popover || {}),
80
+ },
81
+ });
82
+ }
83
+
84
  function destroy() {
85
  const activeElement = getState("activeElement");
86
  const activeStep = getState("activeStep");
 
115
  refresh: () => {
116
  requireRefresh();
117
  },
118
+ drive: (stepIndex: number = 0) => {
119
+ init();
120
+ drive(stepIndex);
121
+ },
122
  highlight: (step: DriveStep) => {
123
  init();
124
  highlight({
src/popover.ts CHANGED
@@ -11,11 +11,12 @@ export type AllowedButtons = "next" | "previous" | "close";
11
 
12
  export type Popover = {
13
  title?: string;
14
- description: string;
15
  side?: Side;
16
  align?: Alignment;
17
 
18
  showButtons?: AllowedButtons[];
 
19
 
20
  popoverClass?: string;
21
 
@@ -57,15 +58,19 @@ export function hidePopover() {
57
 
58
  export function renderPopover(element: Element, step: DriveStep) {
59
  let popover = getState("popover");
60
- if (!popover) {
61
- popover = createPopover();
62
- document.body.appendChild(popover.wrapper);
63
  }
64
 
 
 
 
65
  const {
66
  title,
67
  description,
68
- showButtons: popoverShowButtons = undefined,
 
 
69
  // doneBtnText = 'Done',
70
  closeBtnText = getConfig("closeBtnText") || "Close",
71
  nextBtnText = getConfig("nextBtnText") || "Next &rarr;",
@@ -90,27 +95,31 @@ export function renderPopover(element: Element, step: DriveStep) {
90
  popover.description.style.display = "none";
91
  }
92
 
93
- const showButtonsConfig: AllowedButtons[] =
94
- popoverShowButtons !== undefined ? popoverShowButtons : getConfig("showButtons")!;
95
 
96
  if (showButtonsConfig?.length! > 0) {
97
  popover.footer.style.display = "flex";
98
 
99
- if (!showButtonsConfig.includes("next")) {
100
- popover.nextButton.style.display = "none";
101
- }
102
-
103
- if (!showButtonsConfig.includes("previous")) {
104
- popover.previousButton.style.display = "none";
105
- }
106
-
107
- if (!showButtonsConfig.includes("close")) {
108
- popover.closeButton.style.display = "none";
109
- }
110
  } else {
111
  popover.footer.style.display = "none";
112
  }
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  // Reset the popover position
115
  const popoverWrapper = popover.wrapper;
116
  popoverWrapper.style.display = "block";
 
11
 
12
  export type Popover = {
13
  title?: string;
14
+ description?: string;
15
  side?: Side;
16
  align?: Alignment;
17
 
18
  showButtons?: AllowedButtons[];
19
+ disableButtons?: AllowedButtons[];
20
 
21
  popoverClass?: string;
22
 
 
58
 
59
  export function renderPopover(element: Element, step: DriveStep) {
60
  let popover = getState("popover");
61
+ if (popover) {
62
+ document.body.removeChild(popover.wrapper);
 
63
  }
64
 
65
+ popover = createPopover();
66
+ document.body.appendChild(popover.wrapper);
67
+
68
  const {
69
  title,
70
  description,
71
+ showButtons,
72
+ disableButtons,
73
+
74
  // doneBtnText = 'Done',
75
  closeBtnText = getConfig("closeBtnText") || "Close",
76
  nextBtnText = getConfig("nextBtnText") || "Next &rarr;",
 
95
  popover.description.style.display = "none";
96
  }
97
 
98
+ const showButtonsConfig: AllowedButtons[] = showButtons || getConfig("showButtons")!;
 
99
 
100
  if (showButtonsConfig?.length! > 0) {
101
  popover.footer.style.display = "flex";
102
 
103
+ popover.nextButton.style.display = showButtonsConfig.includes("next") ? 'block' : 'none';
104
+ popover.previousButton.style.display = showButtonsConfig.includes("previous") ? 'block' : 'none';
105
+ popover.closeButton.style.display = showButtonsConfig.includes("close") ? 'block' : 'none';
 
 
 
 
 
 
 
 
106
  } else {
107
  popover.footer.style.display = "none";
108
  }
109
 
110
+ const disabledButtonsConfig: AllowedButtons[] = disableButtons || getConfig('disableButtons')! || [];
111
+ if (disabledButtonsConfig?.includes('next')) {
112
+ popover.nextButton.classList.add("driver-popover-btn-disabled")
113
+ }
114
+
115
+ if (disabledButtonsConfig?.includes('previous')) {
116
+ popover.previousButton.classList.add("driver-popover-btn-disabled")
117
+ }
118
+
119
+ if (disabledButtonsConfig?.includes('close')) {
120
+ popover.closeButton.classList.add("driver-popover-btn-disabled")
121
+ }
122
+
123
  // Reset the popover position
124
  const popoverWrapper = popover.wrapper;
125
  popoverWrapper.style.display = "block";
src/style.css CHANGED
@@ -97,6 +97,11 @@
97
  border-radius: 3px;
98
  }
99
 
 
 
 
 
 
100
  /* Disable the scrolling of parent element if it has an active element*/
101
  :not(body):has(> .driver-active-element) {
102
  overflow: hidden !important;
 
97
  border-radius: 3px;
98
  }
99
 
100
+ .driver-popover-footer .driver-popover-btn-disabled {
101
+ opacity: 0.5;
102
+ pointer-events: none;
103
+ }
104
+
105
  /* Disable the scrolling of parent element if it has an active element*/
106
  :not(body):has(> .driver-active-element) {
107
  overflow: hidden !important;