mirror of
https://github.com/kamranahmedse/driver.js.git
synced 2026-11-01 05:07:12 +08:00
Modernize tooling and add an Astro examples playground
Library: - Update all dev dependencies to latest (vite 4->8, vitest 0.34->4, typescript 5.2->6, @types/node 26, dts-bundle-generator 9, prettier 3.8) - Replace deprecated @vitest/coverage-c8 with @vitest/coverage-v8 - Drop unused ts-node; add jsdom and a jsdom test environment - ESM vite config, modern tsconfig (ES2020, Bundler resolution), Rollup 4 asset-name handling so the CSS still emits as driver.css Playground: - Replace the 1,165-line index.html with playground/, a self-contained Astro app that imports the library from source with HMR - Each scenario is its own file under playground/src/examples grouped into Highlight / Popover & Buttons / Tour / API, listed in a sidebar and run on its own page; shared demo markup lives in Stage.astro - All 41 original scenarios ported and type-checked against the library types - Wire `pnpm dev` to the playground; document local development in the readme
This commit is contained in:
-1165
File diff suppressed because it is too large
Load Diff
+13
-12
@@ -25,7 +25,8 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"dev": "pnpm --dir playground dev",
|
||||
"playground:install": "pnpm --dir playground install",
|
||||
"build": "tsc && vite build && dts-bundle-generator --config ./dts-bundle-generator.config.ts",
|
||||
"test": "vitest",
|
||||
"format": "prettier . --write"
|
||||
@@ -37,17 +38,17 @@
|
||||
"!dist/**/*.js.map"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/jsdom": "^21.1.2",
|
||||
"@types/node": "^20.5.9",
|
||||
"@vitest/coverage-c8": "^0.32.0",
|
||||
"dts-bundle-generator": "^8.0.1",
|
||||
"postcss": "^8.4.29",
|
||||
"postcss-scss": "^4.0.7",
|
||||
"prettier": "^3.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^0.34.3"
|
||||
"@types/jsdom": "^28.0.3",
|
||||
"@types/node": "^26.0.0",
|
||||
"@vitest/coverage-v8": "^4.1.9",
|
||||
"dts-bundle-generator": "^9.5.1",
|
||||
"jsdom": "^29.1.1",
|
||||
"postcss": "^8.5.15",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^3.8.4",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^4.1.9"
|
||||
},
|
||||
"keywords": [
|
||||
"driver.js",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
.astro/
|
||||
node_modules/
|
||||
@@ -0,0 +1,23 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// The playground imports the library straight from source (../src) so changes
|
||||
// to driver.js are reflected instantly with HMR — no build step in between.
|
||||
const driverSource = fileURLToPath(new URL("../src/driver.ts", import.meta.url));
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: {
|
||||
"driver.js": driverSource,
|
||||
},
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
// Allow importing the library source that lives outside this project.
|
||||
allow: [".."],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "driver-playground",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.9",
|
||||
"astro": "^7.0.0",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
Generated
+3369
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
sharp: true
|
||||
minimumReleaseAgeExclude:
|
||||
- "@astrojs/markdown-satteri@0.3.1"
|
||||
- astro@7.0.0
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
- sharp
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
import { exampleGroups } from "../examples";
|
||||
|
||||
interface Props {
|
||||
activeId?: string;
|
||||
}
|
||||
|
||||
const { activeId } = Astro.props;
|
||||
---
|
||||
|
||||
<aside class="sidebar">
|
||||
<a class="sidebar-brand" href="/">
|
||||
driver.js <span>playground</span>
|
||||
</a>
|
||||
|
||||
<nav>
|
||||
{
|
||||
exampleGroups.map(group => (
|
||||
<section class="sidebar-group">
|
||||
<h2>{group.title}</h2>
|
||||
<ul>
|
||||
{group.examples.map(example => (
|
||||
<li>
|
||||
<a href={`/${example.id}`} class={example.id === activeId ? "is-active" : ""}>
|
||||
{example.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
// The shared demo content every example highlights against. Examples reference
|
||||
// these elements by selector, so keep ids/classes stable when editing.
|
||||
---
|
||||
|
||||
<div class="demo-container">
|
||||
<header class="page-header">
|
||||
<h1>driver.js <sup>v1</sup></h1>
|
||||
<p>A lightweight, no-dependency library to drive the user's focus across the page.</p>
|
||||
</header>
|
||||
|
||||
<h2>Highlight any element</h2>
|
||||
<p id="large-paragraph-text">
|
||||
Highlight anything, anywhere on the page — literally anything, including SVG portions, scrollable items and
|
||||
off-screen elements. Pick an example from the sidebar to see it in action.
|
||||
</p>
|
||||
|
||||
<div class="buttons">
|
||||
<button id="card-1" type="button">Card One</button>
|
||||
<button id="card-2" type="button">Card Two</button>
|
||||
<button id="card-3" type="button">Card Three</button>
|
||||
<button id="card-4" type="button">Card Four</button>
|
||||
<button id="card-5" type="button">Card Five</button>
|
||||
<button id="card-6" type="button">Card Six</button>
|
||||
</div>
|
||||
|
||||
<ul class="feature-list">
|
||||
<li>Written in TypeScript</li>
|
||||
<li>Lightweight — only ~5kb gzipped</li>
|
||||
<li>No dependencies</li>
|
||||
<li>MIT Licensed</li>
|
||||
</ul>
|
||||
|
||||
<ul id="hooks-list">
|
||||
<li>Open the console to follow the hooks fired by an example.</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in inventore
|
||||
ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae tempore voluptates!
|
||||
</p>
|
||||
|
||||
<div id="scrollable-area">
|
||||
<p>First — scroll down inside this box to reach the highlighted paragraph.</p>
|
||||
<p>Second — Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
<p id="third-scroll-paragraph">Third — even nested scrollable elements are handled correctly.</p>
|
||||
<p>Fourth — Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
<p>Fifth — Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
<p>Sixth — Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
<p>Seventh — Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
<p>Eighth — Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
<p>Ninth — Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Animi blanditiis consectetur ea eligendi id in inventore
|
||||
ipsa iure laudantium libero, minus molestias necessitatibus nesciunt non omnis, quasi recusandae tempore voluptates!
|
||||
</p>
|
||||
</div>
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
@@ -0,0 +1,111 @@
|
||||
import { driver } from "driver.js";
|
||||
import type { ExampleGroup } from "./types";
|
||||
import { basicTourSteps } from "./shared";
|
||||
|
||||
export const apiGroup: ExampleGroup = {
|
||||
title: "API & Hooks",
|
||||
examples: [
|
||||
{
|
||||
id: "hooks",
|
||||
title: "Lifecycle Hooks",
|
||||
description: "Log every lifecycle hook to the console as elements are highlighted.",
|
||||
run() {
|
||||
const describe = (element?: Element) => element?.textContent?.slice(0, 10) || " - N/A -";
|
||||
|
||||
const driverObj = driver({
|
||||
animate: true,
|
||||
onDeselected: (element, step) => console.log(`Deselected: ${describe(element)}\n${JSON.stringify(step)}`),
|
||||
onHighlightStarted: (element, step) =>
|
||||
console.log(`Highlight Started: ${describe(element)}\n${JSON.stringify(step)}`),
|
||||
onHighlighted: (element, step) => console.log(`Highlighted: ${describe(element)}\n${JSON.stringify(step)}`),
|
||||
onDestroyed: (element, step) => console.log(`Destroyed: ${describe(element)}\n${JSON.stringify(step)}`),
|
||||
});
|
||||
|
||||
driverObj.highlight({
|
||||
element: "#hooks-list",
|
||||
popover: { title: "Hooks", description: "Open the console to follow each hook as it fires." },
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
driverObj.highlight({
|
||||
popover: { title: "Popup Hook", description: "There is no element below this popover." },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
window.setTimeout(() => {
|
||||
driverObj.highlight({
|
||||
element: "ul.feature-list",
|
||||
popover: { description: "Back to an element again." },
|
||||
});
|
||||
}, 2000);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "api-test",
|
||||
title: "API Test",
|
||||
description: "Read tour state (index, first/last step) inside onPopoverRender.",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
animate: true,
|
||||
steps: basicTourSteps,
|
||||
disableActiveInteraction: true,
|
||||
showProgress: true,
|
||||
progressText: "{{current}} of {{total}} done",
|
||||
onPopoverRender: popover => {
|
||||
popover.title.innerHTML = `${driverObj.getActiveIndex()} ${driverObj.hasNextStep() ? "Yes" : "No"} ${
|
||||
driverObj.hasPreviousStep() ? "Yes" : "No"
|
||||
}`;
|
||||
popover.description.innerHTML = `${driverObj.isFirstStep() ? "Yes" : "No"} ${
|
||||
driverObj.isLastStep() ? "Yes" : "No"
|
||||
}`;
|
||||
|
||||
console.log(driverObj.getActiveIndex());
|
||||
console.log(driverObj.getActiveStep());
|
||||
},
|
||||
});
|
||||
|
||||
driverObj.drive(4);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "is-active",
|
||||
title: "Is Active?",
|
||||
description: "Check whether a driver instance is currently active (uses an alert).",
|
||||
run() {
|
||||
alert(driver().isActive());
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "activate-check",
|
||||
title: "Activate and Check",
|
||||
description: "Highlight, then report the active status before and after destroying.",
|
||||
run() {
|
||||
const driverObj = driver({ showButtons: [] });
|
||||
|
||||
driverObj.highlight({
|
||||
element: "#card-1",
|
||||
popover: {
|
||||
title: "Check if driver is active",
|
||||
description: "This will alert the status after 2 seconds.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
alert(`Status: ${driverObj.isActive()}. Destroying driver...`);
|
||||
driverObj.destroy();
|
||||
setTimeout(() => alert(`Status: ${driverObj.isActive()}`), 0);
|
||||
}, 2000);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "destroy",
|
||||
title: "Destroy",
|
||||
description: "Tear down any active driver instance.",
|
||||
run() {
|
||||
driver().destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
import { driver } from "driver.js";
|
||||
import type { ExampleGroup } from "./types";
|
||||
|
||||
export const highlightGroup: ExampleGroup = {
|
||||
title: "Highlight",
|
||||
examples: [
|
||||
{
|
||||
id: "simple-highlight",
|
||||
title: "Simple Highlight",
|
||||
description: "Highlight a single element without animation.",
|
||||
run() {
|
||||
driver({ animate: false }).highlight({
|
||||
element: "#large-paragraph-text",
|
||||
popover: {
|
||||
title: "driver.js",
|
||||
description:
|
||||
"Highlight anything, anywhere on the page. Yes, literally anything including SVG portions and scrollable items.",
|
||||
align: "start",
|
||||
side: "top",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "animated-highlight",
|
||||
title: "Animated Highlight",
|
||||
description: "Highlight with animation and lifecycle hooks logged to the console.",
|
||||
run() {
|
||||
driver({
|
||||
animate: true,
|
||||
popoverOffset: 10,
|
||||
stagePadding: 10,
|
||||
onDeselected: (element, step) => console.log("Deselected element", element, step),
|
||||
onHighlightStarted: (element, step) => console.log("Started highlighting element", element, step),
|
||||
onHighlighted: (element, step) => console.log("Highlighted element", element, step),
|
||||
}).highlight({
|
||||
element: "h2",
|
||||
popover: {
|
||||
title: "MIT License",
|
||||
description: "A lightweight, no-dependency JavaScript engine to drive the user's focus.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "transition-highlight",
|
||||
title: "Transition Highlight",
|
||||
description: "Re-highlight different elements to watch the popover transition between them.",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
animate: true,
|
||||
onDeselected: (element, step) => console.log("Deselected element", element, step),
|
||||
onHighlightStarted: (element, step) => console.log("Started highlighting element", element, step),
|
||||
onHighlighted: (element, step) => console.log("Highlighted element", element, step),
|
||||
});
|
||||
|
||||
driverObj.highlight({
|
||||
popover: { title: "driver.js", description: "Highlight anything, anywhere on the page." },
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
driverObj.highlight({
|
||||
element: ".buttons button:first-child",
|
||||
popover: { title: "driver.js", description: "Highlight anything, anywhere on the page." },
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
window.setTimeout(() => {
|
||||
driverObj.highlight({
|
||||
popover: { title: "driver.js", description: "Highlight anything, anywhere on the page." },
|
||||
});
|
||||
}, 4000);
|
||||
|
||||
window.setTimeout(() => {
|
||||
driverObj.highlight({ element: "h2", popover: { description: "driver.js" } });
|
||||
}, 6000);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "off-screen-highlight",
|
||||
title: "Off Screen Highlight",
|
||||
description: "Highlight an element that is taller than the viewport.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: ".demo-container",
|
||||
popover: {
|
||||
title: "Off Screen Highlight",
|
||||
description: "The page scrolls to bring partially off-screen elements into view automatically.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "nested-highlight",
|
||||
title: "Nested Element Highlight",
|
||||
description: "Highlight a deeply nested inline element.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: ".page-header h1 sup",
|
||||
popover: {
|
||||
title: "Nested Highlight",
|
||||
description: "Even tiny nested elements are highlighted precisely without z-index hacks.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "dark-highlight",
|
||||
title: "Super Dark Highlight",
|
||||
description: "Increase the overlay opacity for a darker backdrop.",
|
||||
run() {
|
||||
driver({ animate: true, overlayOpacity: 0.9 }).highlight({ element: "ul.feature-list" });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "dim-highlight",
|
||||
title: "Super Dim Highlight",
|
||||
description: "Lower the overlay opacity for a subtle backdrop.",
|
||||
run() {
|
||||
driver({ animate: true, overlayOpacity: 0.2 }).highlight({ element: ".buttons" });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "backdrop-color",
|
||||
title: "Backdrop Color",
|
||||
description: "Tint the overlay with a custom colour.",
|
||||
run() {
|
||||
driver({ overlayColor: "blue", overlayOpacity: 0.3 }).highlight({ element: "#card-1" });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "scrollable-area",
|
||||
title: "Scrollable Area",
|
||||
description: "Highlight an element inside a scrollable container.",
|
||||
run() {
|
||||
driver({ animate: true }).highlight({ element: "#scrollable-area" });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "inner-scroll-area",
|
||||
title: "Inner Scroll Area",
|
||||
description: "Highlight a paragraph nested inside the scrollable container.",
|
||||
run() {
|
||||
driver({ animate: true }).highlight({ element: "#third-scroll-paragraph" });
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "no-element",
|
||||
title: "No Element",
|
||||
description: "Show a popover that is not attached to any element.",
|
||||
run() {
|
||||
driver({
|
||||
animate: true,
|
||||
onDestroyed: (element, step) => console.log("Close modal", element, step),
|
||||
onDeselected: (element, step) => console.log("Deselected element", element, step),
|
||||
onHighlightStarted: (element, step) => console.log("Started highlighting element", element, step),
|
||||
onHighlighted: (element, step) => console.log("Highlighted element", element, step),
|
||||
}).highlight({
|
||||
popover: {
|
||||
showButtons: [],
|
||||
description:
|
||||
"<div class='gif-popover'><img style='max-width: 100%' src='https://i.imgur.com/EAQhHu5.gif' /><p>Go and build something cool!</p></div>",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "disallow-close",
|
||||
title: "Disallow Close",
|
||||
description: "Prevent the overlay click from closing the highlight.",
|
||||
run() {
|
||||
driver({ animate: true, allowClose: false }).highlight({ element: ".buttons" });
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { Example, ExampleGroup } from "./types";
|
||||
import { highlightGroup } from "./highlight";
|
||||
import { popoverGroup } from "./popover";
|
||||
import { tourGroup } from "./tour";
|
||||
import { apiGroup } from "./api";
|
||||
|
||||
export const exampleGroups: ExampleGroup[] = [highlightGroup, popoverGroup, tourGroup, apiGroup];
|
||||
|
||||
export const examples: Example[] = exampleGroups.flatMap(group => group.examples);
|
||||
|
||||
export function findExample(id: string | undefined): Example | undefined {
|
||||
return examples.find(example => example.id === id);
|
||||
}
|
||||
|
||||
export type { Example, ExampleGroup };
|
||||
@@ -0,0 +1,233 @@
|
||||
import { driver } from "driver.js";
|
||||
import type { ExampleGroup } from "./types";
|
||||
|
||||
export const popoverGroup: ExampleGroup = {
|
||||
title: "Popover & Buttons",
|
||||
examples: [
|
||||
{
|
||||
id: "no-buttons",
|
||||
title: "No Buttons",
|
||||
description: "Highlight shows no buttons by default.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: "#card-1",
|
||||
popover: {
|
||||
title: "No Buttons",
|
||||
description: "No buttons are shown by default for a highlight. Opt into the ones you want.",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "selected-buttons",
|
||||
title: "Selected Buttons",
|
||||
description: "Choose which buttons appear via the popover config.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: "#card-2",
|
||||
popover: {
|
||||
title: "Close Only",
|
||||
showButtons: ["close"],
|
||||
description: "Pass `showButtons` on the popover to display only the buttons you need.",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "next-button",
|
||||
title: "Next Button",
|
||||
description: "Show only the next and close buttons.",
|
||||
run() {
|
||||
const driverObj = driver();
|
||||
driverObj.highlight({
|
||||
element: "#card-3",
|
||||
popover: {
|
||||
title: "Next and Close",
|
||||
showButtons: ["close", "next"],
|
||||
description: "This one only has next and close buttons, nothing else.",
|
||||
onNextClick: (step, element, opts) => {
|
||||
console.log("Next Clicked", step, element, opts);
|
||||
opts.driver.destroy();
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "previous-button",
|
||||
title: "Previous Button",
|
||||
description: "Show only the previous and close buttons.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: "#card-4",
|
||||
popover: {
|
||||
title: "Previous and Close",
|
||||
showButtons: ["close", "previous"],
|
||||
description: "This one only has previous and close buttons, nothing else.",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "next-prev-button",
|
||||
title: "Next & Previous",
|
||||
description: "Show the next and previous buttons together.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: "#card-5",
|
||||
popover: {
|
||||
title: "Next and Previous Only",
|
||||
showButtons: ["next", "previous"],
|
||||
description: "This one only has next and previous buttons.",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "close-button",
|
||||
title: "Close Button",
|
||||
description: "Show only the close button.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: "#card-6",
|
||||
popover: {
|
||||
title: "Close Only",
|
||||
showButtons: ["close"],
|
||||
description: "This one only has the close button.",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "button-texts",
|
||||
title: "Button Texts",
|
||||
description: "Override the button labels from config and popover.",
|
||||
run() {
|
||||
driver({ prevBtnText: "<——", nextBtnText: "——>" }).highlight({
|
||||
element: "#card-1",
|
||||
popover: {
|
||||
side: "left",
|
||||
title: "Custom Button Text",
|
||||
showButtons: ["close", "next", "previous"],
|
||||
nextBtnText: "==>",
|
||||
prevBtnText: "<==",
|
||||
description: "These button labels are configured on the popover.",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "disabled-buttons",
|
||||
title: "Disabled Buttons",
|
||||
description: "Selectively disable individual buttons.",
|
||||
run() {
|
||||
driver().highlight({
|
||||
element: "#card-2",
|
||||
popover: {
|
||||
title: "Disabled Buttons",
|
||||
description: "You can selectively disable buttons as well.",
|
||||
showButtons: ["next", "previous", "close"],
|
||||
disableButtons: ["next", "previous"],
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "button-listeners",
|
||||
title: "Button Listeners",
|
||||
description: "Listen to button clicks globally (uses alerts).",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
onNextClick: () => alert("Next Clicked"),
|
||||
onPrevClick: () => alert("Previous Clicked"),
|
||||
onCloseClick: () => driverObj.destroy(),
|
||||
});
|
||||
|
||||
driverObj.highlight({
|
||||
popover: {
|
||||
title: "Global Button Listener",
|
||||
description: "You can listen to the button clicks globally.",
|
||||
showButtons: ["close", "next", "previous"],
|
||||
onPrevClick: () => alert("Overriding — Previous Clicked"),
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "custom-classes",
|
||||
title: "Custom Classes",
|
||||
description: "Apply a custom class to style the popover.",
|
||||
run() {
|
||||
driver({ popoverClass: "custom-driver-popover" }).highlight({
|
||||
popover: {
|
||||
popoverClass: "custom-driver-popover",
|
||||
title: "Custom Classes",
|
||||
description: "The popover and its buttons use custom classes for styling.",
|
||||
showButtons: ["close", "next", "previous"],
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "popover-hook",
|
||||
title: "Popover Hook",
|
||||
description: "Modify the popover in the onPopoverRender hook.",
|
||||
run() {
|
||||
driver({
|
||||
onPopoverRender: popover => {
|
||||
popover.title.innerText = "Modified Parent";
|
||||
},
|
||||
}).highlight({
|
||||
element: ".page-header",
|
||||
popover: {
|
||||
title: "Page Title",
|
||||
description: "The title is rewritten in the render hook.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
onPopoverRender: popover => {
|
||||
popover.title.innerText = "Modified";
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "popover-custom-button",
|
||||
title: "Custom Footer Button",
|
||||
description: "Append a custom button to the popover footer.",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
onPopoverRender: popover => {
|
||||
const firstButton = document.createElement("button");
|
||||
firstButton.innerText = "First";
|
||||
popover.footerButtons.appendChild(firstButton);
|
||||
firstButton.addEventListener("click", () => console.log("First Button Clicked"));
|
||||
},
|
||||
steps: [
|
||||
{ popover: { title: "Some title", description: "Some description" } },
|
||||
{ popover: { title: "Another title", description: "Some description" } },
|
||||
{ popover: { title: "Yet another title", description: "Some description" } },
|
||||
],
|
||||
});
|
||||
|
||||
driverObj.drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "padding-change",
|
||||
title: "Padding & Radius",
|
||||
description: "Tweak the stage padding, radius and popover offset.",
|
||||
run() {
|
||||
driver({ stagePadding: 0, popoverOffset: 20, stageRadius: 10 }).highlight({
|
||||
element: "#card-3",
|
||||
popover: {
|
||||
title: "Padding & Radius",
|
||||
description: "The stage padding, radius and popover offset are all customised here.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { DriveStep } from "driver.js";
|
||||
|
||||
// Reused across the tour examples so they stay in sync. Targets elements that
|
||||
// live in the shared <Stage /> component.
|
||||
export const basicTourSteps: DriveStep[] = [
|
||||
{
|
||||
element: ".page-header",
|
||||
popover: {
|
||||
title: "Driver.js",
|
||||
description: "A powerful, highly customisable and dependency-free way to drive the user's focus across the page.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: ".page-header h1",
|
||||
popover: {
|
||||
title: "No Stacking Issues",
|
||||
description: "The popover is positioned without juggling z-indexes, so there are no positional surprises.",
|
||||
side: "left",
|
||||
align: "start",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: ".page-header sup",
|
||||
popover: {
|
||||
title: "Improved Hooks",
|
||||
description: "Hooks let you run logic before and after each step is highlighted.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: "No Element",
|
||||
description: "You can have popovers without elements as well — handy for intros and outros.",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: ".buttons",
|
||||
popover: {
|
||||
title: "Buttons",
|
||||
description: "Here are some buttons to highlight as a group.",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: "#scrollable-area",
|
||||
popover: {
|
||||
title: "Scrollable Areas",
|
||||
description: "Tours through scrollable elements work without any extra configuration.",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: "#third-scroll-paragraph",
|
||||
popover: {
|
||||
title: "Nested Scrolls",
|
||||
description: "Even nested scrollable elements are brought into view.",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,254 @@
|
||||
import { driver } from "driver.js";
|
||||
import type { ExampleGroup } from "./types";
|
||||
import { basicTourSteps } from "./shared";
|
||||
|
||||
export const tourGroup: ExampleGroup = {
|
||||
title: "Tour",
|
||||
examples: [
|
||||
{
|
||||
id: "animated-tour",
|
||||
title: "Animated Tour",
|
||||
description: "A multi-step tour with progress and navigation buttons.",
|
||||
run() {
|
||||
driver({
|
||||
showProgress: true,
|
||||
showButtons: ["next", "previous", "close"],
|
||||
steps: basicTourSteps,
|
||||
}).drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "non-animated-tour",
|
||||
title: "Non-Animated Tour",
|
||||
description: "The same tour with animation disabled and a tinted overlay.",
|
||||
run() {
|
||||
driver({
|
||||
animate: false,
|
||||
overlayColor: "blue",
|
||||
overlayOpacity: 0.3,
|
||||
showProgress: true,
|
||||
steps: basicTourSteps,
|
||||
}).drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "async-tour",
|
||||
title: "Asynchronous Tour",
|
||||
description: "Control the tour flow and create elements on the fly via onNextClick.",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
animate: true,
|
||||
overlayOpacity: 0.3,
|
||||
showProgress: true,
|
||||
progressText: "{{current}} / {{total}}",
|
||||
steps: [
|
||||
{
|
||||
element: ".page-header",
|
||||
popover: {
|
||||
title: "Async Driver.js",
|
||||
description: "Override `onNextClick` to take full control over when the tour advances.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: ".page-header h1",
|
||||
popover: {
|
||||
title: "Async Test",
|
||||
description: "By overriding `onNextClick` you get control over the tour.",
|
||||
side: "left",
|
||||
align: "start",
|
||||
onNextClick: () => {
|
||||
const newDiv = document.querySelector<HTMLDivElement>(".dynamic-el") || document.createElement("div");
|
||||
|
||||
newDiv.innerHTML = "This is a new Element";
|
||||
newDiv.style.display = "block";
|
||||
newDiv.style.padding = "20px";
|
||||
newDiv.style.backgroundColor = "black";
|
||||
newDiv.style.color = "white";
|
||||
newDiv.style.fontSize = "14px";
|
||||
newDiv.style.position = "fixed";
|
||||
newDiv.style.top = `${Math.random() * (500 - 30) + 30}px`;
|
||||
newDiv.style.left = `${Math.random() * (500 - 30) + 30}px`;
|
||||
newDiv.className = "dynamic-el";
|
||||
|
||||
document.body.appendChild(newDiv);
|
||||
|
||||
driverObj.moveNext();
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
element: ".dynamic-el",
|
||||
onDeselected: element => {
|
||||
element?.parentElement?.removeChild(element);
|
||||
},
|
||||
popover: {
|
||||
title: "Dynamic Elements",
|
||||
description: "This element was created right before we moved here.",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: ".page-header sup",
|
||||
popover: {
|
||||
title: "Improved Hooks",
|
||||
description: "Hooks let you run logic before and after each step is highlighted.",
|
||||
side: "bottom",
|
||||
align: "start",
|
||||
},
|
||||
},
|
||||
{
|
||||
popover: {
|
||||
title: "No Element",
|
||||
description: "You can now have popovers without elements as well.",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: "#scrollable-area",
|
||||
popover: {
|
||||
title: "Scrollable Areas",
|
||||
description: "There are no issues with scrollable element tours either.",
|
||||
},
|
||||
},
|
||||
{
|
||||
element: "#third-scroll-paragraph",
|
||||
popover: {
|
||||
title: "Nested Scrolls",
|
||||
description: "Even nested scrollable elements work.",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
driverObj.drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "confirm-exit-tour",
|
||||
title: "Confirm on Exit",
|
||||
description: "Ask for confirmation before the tour is destroyed.",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
animate: true,
|
||||
overlayColor: "green",
|
||||
overlayOpacity: 0.3,
|
||||
steps: basicTourSteps,
|
||||
onDestroyStarted: () => {
|
||||
if (driverObj.hasNextStep() && confirm("Are you sure?")) {
|
||||
driverObj.destroy();
|
||||
} else {
|
||||
driverObj.destroy();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
driverObj.drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "progress-tour",
|
||||
title: "Progress Text",
|
||||
description: "Show the default progress indicator.",
|
||||
run() {
|
||||
driver({ animate: true, steps: basicTourSteps, showProgress: true }).drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "progress-tour-template",
|
||||
title: "Progress Text Template",
|
||||
description: "Customise the progress text template.",
|
||||
run() {
|
||||
driver({
|
||||
animate: true,
|
||||
steps: basicTourSteps,
|
||||
showProgress: true,
|
||||
progressText: "{{current}} of {{total}} done",
|
||||
}).drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "reconfigure-steps",
|
||||
title: "Re-Configuring Steps",
|
||||
description: "Replace the steps after the driver has been created.",
|
||||
run() {
|
||||
const driverObj = driver({ animate: true, steps: basicTourSteps, showProgress: true });
|
||||
|
||||
driverObj.setSteps([
|
||||
{ element: "h1", popover: { description: "This is a new description" } },
|
||||
{ element: "p", popover: { description: "This is another new description" } },
|
||||
]);
|
||||
|
||||
driverObj.drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "disable-keyboard-control",
|
||||
title: "Disable Keyboard Control",
|
||||
description: "Turn off arrow-key and escape navigation.",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
animate: true,
|
||||
steps: basicTourSteps,
|
||||
showProgress: true,
|
||||
allowKeyboardControl: false,
|
||||
});
|
||||
|
||||
driverObj.setSteps([
|
||||
{ element: "h1", popover: { description: "This is a new description" } },
|
||||
{ element: "p", popover: { description: "This is another new description" } },
|
||||
]);
|
||||
|
||||
driverObj.drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tour-button-listeners",
|
||||
title: "Tour Button Listeners",
|
||||
description: "Drive the tour manually from global button listeners (uses alerts).",
|
||||
run() {
|
||||
const driverObj = driver({
|
||||
onNextClick: () => {
|
||||
alert("Next Clicked");
|
||||
driverObj.moveNext();
|
||||
},
|
||||
onPrevClick: () => {
|
||||
alert("Previous Clicked");
|
||||
driverObj.movePrevious();
|
||||
},
|
||||
onCloseClick: () => driverObj.destroy(),
|
||||
steps: [
|
||||
{ popover: { title: "Some title", description: "Some description" } },
|
||||
{ popover: { title: "Another title", description: "Some description" } },
|
||||
{ popover: { title: "Yet another title", description: "Some description" } },
|
||||
],
|
||||
});
|
||||
|
||||
driverObj.drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "click-overlay-to-next",
|
||||
title: "Click Overlay to Next",
|
||||
description: "Advance the tour when the overlay is clicked.",
|
||||
run() {
|
||||
driver({
|
||||
animate: true,
|
||||
overlayClickBehavior: "nextStep",
|
||||
steps: basicTourSteps,
|
||||
}).drive();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "click-overlay-to-handle",
|
||||
title: "Custom Overlay Click",
|
||||
description: "Handle overlay clicks with a custom callback.",
|
||||
run() {
|
||||
driver({
|
||||
animate: true,
|
||||
overlayClickBehavior: () => alert("Clicking me"),
|
||||
steps: basicTourSteps,
|
||||
}).drive();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
export type Example = {
|
||||
// Unique slug, used as the example's URL (/highlight-simple) and DOM hook.
|
||||
id: string;
|
||||
// Sidebar label.
|
||||
title: string;
|
||||
// One-line explanation shown above the stage.
|
||||
description: string;
|
||||
// Runs the example against the shared demo stage.
|
||||
run: () => void;
|
||||
};
|
||||
|
||||
export type ExampleGroup = {
|
||||
title: string;
|
||||
examples: Example[];
|
||||
};
|
||||
@@ -0,0 +1,275 @@
|
||||
---
|
||||
import Sidebar from "../components/Sidebar.astro";
|
||||
import Stage from "../components/Stage.astro";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
activeId?: string;
|
||||
}
|
||||
|
||||
const { title, description, activeId } = Astro.props;
|
||||
const pageTitle = title ? `${title} · driver.js playground` : "driver.js playground";
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>{pageTitle}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<Sidebar activeId={activeId} />
|
||||
<main class="content">
|
||||
{
|
||||
title && (
|
||||
<div class="example-heading">
|
||||
<h1>{title}</h1>
|
||||
{description && <p>{description}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<slot />
|
||||
<Stage />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style is:global>
|
||||
:root {
|
||||
--border: #e5e7eb;
|
||||
--muted: #6b7280;
|
||||
--accent: #2563eb;
|
||||
--bg: #ffffff;
|
||||
--sidebar-bg: #f9fafb;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
font-size: 14px;
|
||||
color: #111827;
|
||||
background: var(--bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
*:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
align-self: start;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
border-right: 1px solid var(--border);
|
||||
background: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-brand span {
|
||||
color: var(--muted);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.sidebar-group {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.sidebar-group h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.sidebar-group ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sidebar-group li a {
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
color: #374151;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-group li a:hover {
|
||||
background: #eef2ff;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.sidebar-group li a.is-active {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.content {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.example-heading {
|
||||
max-width: 640px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
.example-heading h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.example-heading p {
|
||||
margin-top: 6px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Demo stage */
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.demo-container p {
|
||||
line-height: 1.5;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.demo-container h1,
|
||||
.demo-container h2 {
|
||||
margin: 30px 0 10px;
|
||||
}
|
||||
|
||||
.demo-container h1 {
|
||||
font-size: 48px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-container h1 sup {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.feature-list,
|
||||
#hooks-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 20px 10px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.feature-list li:before,
|
||||
#hooks-list li:before {
|
||||
content: "•";
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.demo-container button {
|
||||
all: unset;
|
||||
border: 1px solid #ccc;
|
||||
padding: 5px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#scrollable-area {
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 50px 0;
|
||||
}
|
||||
|
||||
/* Custom popover theme used by the "Custom Classes" example */
|
||||
.driver-popover.custom-driver-popover {
|
||||
min-width: 450px;
|
||||
background: #2d2d2d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.driver-popover.custom-driver-popover .driver-popover-title {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.driver-popover.custom-driver-popover .driver-popover-description {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.driver-popover.custom-driver-popover button {
|
||||
background: #454545;
|
||||
border-color: #454545;
|
||||
text-shadow: none;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
.driver-popover.custom-driver-popover button:hover {
|
||||
background: #575757;
|
||||
}
|
||||
|
||||
.gif-popover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gif-popover img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gif-popover p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
import PlaygroundLayout from "../layouts/PlaygroundLayout.astro";
|
||||
import { examples } from "../examples";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return examples.map(example => ({
|
||||
params: { example: example.id },
|
||||
props: { title: example.title, description: example.description },
|
||||
}));
|
||||
}
|
||||
|
||||
const { example } = Astro.params;
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<PlaygroundLayout title={title} description={description} activeId={example}>
|
||||
<div id="example-runner" data-example-id={example}></div>
|
||||
</PlaygroundLayout>
|
||||
|
||||
<script>
|
||||
import { findExample } from "../examples";
|
||||
|
||||
const runner = document.getElementById("example-runner");
|
||||
const example = findExample(runner?.dataset.exampleId);
|
||||
|
||||
// Each example page auto-runs its scenario against the shared stage. Navigating
|
||||
// to another example is a full page load, so the stage always starts clean.
|
||||
example?.run();
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import PlaygroundLayout from "../layouts/PlaygroundLayout.astro";
|
||||
---
|
||||
|
||||
<PlaygroundLayout
|
||||
title="Playground"
|
||||
description="Pick an example from the sidebar — each one runs against the demo content below. The library is imported straight from ../src, so edits hot-reload instantly."
|
||||
/>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"driver.js": ["../src/driver.ts"]
|
||||
}
|
||||
},
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
Generated
+1019
-965
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,25 @@ Driver.js is written in Vanilla TypeScript, has zero dependencies and is highly
|
||||
|
||||
<br>
|
||||
|
||||
## Developing locally
|
||||
|
||||
The library lives in `src/`. To try your changes against a set of live examples, run the playground — an Astro app in `playground/` that imports the library straight from source, so edits hot-reload instantly:
|
||||
|
||||
```sh
|
||||
pnpm install # install the library's dev dependencies
|
||||
pnpm run playground:install # one-time install of the playground's dependencies
|
||||
pnpm dev # start the playground
|
||||
```
|
||||
|
||||
Each example lives in its own file under `playground/src/examples/` and shows up as a page in the sidebar. To add one, drop an entry into the relevant group (`highlight.ts`, `popover.ts`, `tour.ts`, `api.ts`).
|
||||
|
||||
Other useful scripts:
|
||||
|
||||
```sh
|
||||
pnpm build # type-check and build the distributable library into dist/
|
||||
pnpm test # run the unit tests (Vitest)
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
||||
Feel free to submit pull requests, create issues or spread the word.
|
||||
|
||||
+8
-7
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"target": "ES2019",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2019", "DOM"],
|
||||
"moduleResolution": "Node",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": ["vite/client", "node"]
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/*.test.ts", "node_modules",
|
||||
"tests/**", ".history/**"]
|
||||
"exclude": ["**/*.test.ts", "node_modules", "tests/**", ".history/**"]
|
||||
}
|
||||
|
||||
+8
-5
@@ -1,4 +1,4 @@
|
||||
/// <reference types="vitest" />
|
||||
/// <reference types="vitest/config" />
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
@@ -12,10 +12,10 @@ const fileName = {
|
||||
|
||||
const formats = Object.keys(fileName) as Array<keyof typeof fileName>;
|
||||
|
||||
module.exports = defineConfig({
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
build: {
|
||||
target: "ES2019",
|
||||
target: "es2020",
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, "src/driver.ts"),
|
||||
name: packageName,
|
||||
@@ -25,10 +25,13 @@ module.exports = defineConfig({
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: assetInfo => {
|
||||
return assetInfo.name === "style.css" ? `driver.css` : assetInfo.name as string;
|
||||
const name = assetInfo.names?.[0] ?? assetInfo.name;
|
||||
return name?.endsWith(".css") ? `driver.css` : (name as string);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {},
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user