Package Exports
- @sammy-labs/walkthroughs
- @sammy-labs/walkthroughs/dist/index.css
Readme
@sammy-labs/walkthroughs
This package provides a simple, Provider + Hook architecture to integrate Sammy's interactive walkthroughs into your React (or Next.js) application. It manages global state, highlights elements on the page, and guides the user step-by-step through a sequence of interactions.
Key features include:
- Global
WalkthroughProvider
to manage authentication tokens, driver instances, and global config. - React Hook
useWalkthrough
to start, stop, or control walkthroughs anywhere in your app. - Utility methods for fetching and transforming walkthrough data (from pre-fetched).
- Highly configurable overlay, popover, animations, and interaction settings.
Table of Contents
- Installation
- Core Concepts
- Quick Start
- Usage in Next.js
- Usage in a Plasmo Chrome Extension
- API Reference
- Examples
- License
Installation
npm install @sammy-labs/walkthroughs
# or
yarn add @sammy-labs/walkthroughs
Core Concepts
WalkthroughProvider
A React provider that must wrap the part of your app which uses the walkthroughs. It manages:- The short-lived auth token (JWT) for API calls.
- The current walkthrough state (highlighted element, popover, etc.).
- Global configuration (e.g., debug mode, default API URL).
useWalkthrough Hook
A React Hook that gives you access to simple methods to:startWithData(flowData)
: Start a walkthrough using pre-fetched or pre-built data.stop()
: Stop any active walkthrough.isActive()
: Check if a walkthrough is currently running.- Additional convenience and configuration methods.
flow-guide utilities
Internally, the package has utilities for:- Fetching walkthrough data from an API.
- Constructing or parsing the data format into steps that the driver can execute.
These are used behind the scenes by methods like startWithId
or startWithData
.
Important Principle
The
<WalkthroughProvider>
is the single source of truth for starting/stopping walkthroughs. All calls to start or stop a walkthrough should happen via the Hook (useWalkthrough
) that reads from this Provider.
Quick Start
Install the package:
npm install @sammy-labs/walkthroughs
Wrap your app (or a high-level part of it) with the
<WalkthroughProvider>
:import React from "react"; import { WalkthroughProvider } from "@sammy-labs/walkthroughs"; function MyApp({ Component, pageProps }) { return ( <WalkthroughProvider token={/* your JWT token, if any */} baseUrl="https://api.com" onTokenExpired={() => console.log("Token expired!")} onError={(err) => console.error("Walkthrough error:", err)} driverConfig={{ overlayOpacity: 0.8 }} config={{ debug: true }} > <Component {...pageProps} /> </WalkthroughProvider> ); } export default MyApp;
The
token
here is typically fetched from your API or via some authentication method.Use the
useWalkthrough
hook in any React component to start a walkthrough:import React from "react"; import { useWalkthrough } from "@sammy-labs/walkthroughs"; export function MyPage() { const { startWithId, stop, isActive } = useWalkthrough(); let data = [...] const handleStart = async () => { const success = await startWithdata(data); if (!success) { alert("Failed to start walkthrough."); } }; return ( <div> <button onClick={handleStart}>Start Walkthrough #1234</button> {isActive() && <button onClick={stop}>Stop Walkthrough</button>} </div> ); }
That's it! The library will handle highlighting and guiding the user.
Usage in Next.js
Below is a simplified pattern for integrating the library into a Next.js application. You can adapt it to your framework of choice.
Provider Example
You'll want a custom provider in your Next.js app that fetches an JWT token (if needed) and passes it into <WalkthroughProvider>
. For example:
"use client";
import React, { useState, useEffect, useCallback } from "react";
import { WalkthroughProvider } from "@sammy-labs/walkthroughs";
import type { ReactNode } from "react";
export default function SammyWalkthroughProvider({
children,
}: {
children: ReactNode;
}) {
const [token, setToken] = useState<string | undefined>(undefined);
const [loading, setLoading] = useState(true);
// Typically from an .env file
const baseUrl = process.env.NEXT_PUBLIC_SAMMY_BASE_URL || "";
const fetchToken = useCallback(async () => {
try {
// Your logic to fetch/generate a token
const resp = await fetch("/api/auth/sammy");
const { token: newToken } = await resp.json();
setToken(newToken);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
fetchToken();
}, [fetchToken]);
const handleTokenExpired = useCallback(() => {
console.log("Token expired, fetching again...");
fetchToken();
}, [fetchToken]);
if (loading) {
return <div>Loading token...</div>;
}
return (
<WalkthroughProvider
token={token}
baseUrl={baseUrl}
onTokenExpired={handleTokenExpired}
onError={(err) => console.warn("Walkthrough error:", err)}
driverConfig={{ overlayOpacity: 0.8 }}
config={{ debug: true }}
>
{children}
</WalkthroughProvider>
);
}
Then in your Next.js layout.tsx
or wherever you set up root providers:
// app/layout.tsx
import SammyWalkthroughProvider from "./providers/sammy-walkthrough-provider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<SammyWalkthroughProvider>{children}</SammyWalkthroughProvider>
</body>
</html>
);
}
Using the Hook in Pages or Components
Within your page (e.g., app/login/page.tsx
or src/pages/login.tsx
), you can:
"use client";
import { useState } from "react";
import { useWalkthrough, WalkthroughResponse } from "@sammy-labs/walkthroughs";
export default function LoginPage() {
const { startWithData, stop, isActive } = useWalkthrough();
const [someData, setSomeData] = useState<WalkthroughResponse | null>(null);
const handleStart = async () => {
if (!someData) {
return;
}
const success = await startWithData(someData);
if (!success) {
alert("Failed to start walkthrough.");
}
};
return (
<div>
<button onClick={handleStart} disabled={!someData}>
Start Walkthrough
</button>
{isActive() && <button onClick={stop}>Stop Walkthrough</button>}
</div>
);
}
You can fetch or generate someData
from your server or from an API endpoint-this library will parse it to steps automatically.
API Reference
<WalkthroughProvider />
Prop | Type | Description |
---|---|---|
token | string | null |
The authentication token (JWT). If not provided, certain API calls may fail. |
baseUrl | string |
The base API URL for fetching walkthrough data. Defaults to http://localhost:8000 . |
onTokenExpired | () => void |
Callback fired if the token is invalid or expires (e.g., 401 responses). You can re-fetch a new token here. |
onError | (error: FlowError) => void |
Callback for reporting any error encountered while starting or running a walkthrough. |
driverConfig | Partial<DriverConfig> |
Fine-grained configuration for the highlight driver (overlay color, opacity, popover offset, etc.). |
config | Partial<WalkthroughConfig> |
Global configuration (debug, max element find attempts, wait time, etc.). |
logoUrl | string |
Optional logo to display in the popover. |
children | ReactNode |
The rest of your app. |
Example:
<WalkthroughProvider
token={myToken}
baseUrl="https://myapi.com"
onTokenExpired={() => {
/* re-fetch token */
}}
onError={(err) => console.error("Walkthrough Error:", err)}
driverConfig={{ overlayOpacity: 0.8, stagePadding: 8 }}
config={{ debug: true, flowIdQueryParam: "sammy_flow_id" }}
>
<App />
</WalkthroughProvider>
useWalkthrough(options?)
A Hook that returns convenience methods and state.
Signature:
function useWalkthrough(options?: {
checkQueryOnMount?: boolean;
onError?: (error: FlowError) => void;
waitTime?: number;
driverConfig?: Partial<DriverConfig>;
config?: Partial<WalkthroughConfig>;
disableRedirects?: boolean;
autoStartPendingWalkthrough?: boolean;
}): {
// Methods
startWithId(flowId: string | number): Promise<boolean>;
startWithData(
data: WalkthroughResponse | WalkthroughSearchResult | any
): Promise<boolean>;
stop(): boolean;
isActive(): boolean;
configure(config: Partial<DriverConfig>): void;
configureGlobal(config: Partial<WalkthroughConfig>): void;
// State
state: {
isTokenValid: boolean;
isLoading: boolean;
error: FlowError | null;
token: string | null;
baseUrl: string;
isActive: boolean;
config: WalkthroughConfig;
};
};
Methods
startWithId(flowId)
Fetches walkthrough data from the API (usingflowId
) and starts the flow if successful.- Returns a Promise of
true
if successful,false
otherwise.
- Returns a Promise of
startWithData(data)
Starts a walkthrough using pre-fetched or user-provided data in the correct shape.- Accepts either a
WalkthroughResponse
or an older search result format. - Returns a Promise of
true
if started successfully,false
otherwise.
- Accepts either a
stop()
Stops any active walkthrough and cleans up the highlight/overlay.isActive()
Returnstrue
if a walkthrough is currently active.configure(driverConfig)
Dynamically updates the driver configuration.configureGlobal(globalConfig)
Dynamically updates the global configuration.
State
- isTokenValid:
boolean
Whether the last token validation was successful. - isLoading:
boolean
Indicates if the library is currently fetching the flow data. - error:
FlowError \| null
Contains any recent error encountered. - token:
string \| null
Current token provided to the library. - baseUrl:
string
Current base URL for API calls. - isActive:
boolean
Shortcut forisActive()
. - config:
WalkthroughConfig
The merged global configuration.
Global and Driver Config
Global Configuration (WalkthroughConfig
)
export type WalkthroughConfig = {
flowIdQueryParam: string; // Default: "sammy_flow_id"
waitTimeAfterLoadMs: number; // Default: 5000
maxElementFindAttempts: number; // Default: 3
elementFindTimeoutMs: number; // Default: 10000
debug: boolean; // Default: false
apiBaseUrl: string; // Default: "http://localhost:8000"
logoUrl: string; // Default: ""
};
You can pass a partial override to <WalkthroughProvider config={...}>
or to the hook. This controls the overall behavior (like how many attempts to locate elements, how long to wait for them, etc.).
Driver Configuration (DriverConfig
)
export interface DriverConfig {
steps?: DriveStep[];
animate?: boolean;
overlayColor?: string;
overlayOpacity?: number;
smoothScroll?: boolean;
allowClose?: boolean;
overlayClickBehavior?: "close" | "nextStep";
stagePadding?: number;
stageRadius?: number;
disableActiveInteraction?: boolean;
allowKeyboardControl?: boolean;
popoverClass?: string;
popoverOffset?: number;
showButtons?: AllowedButtons[];
disableButtons?: AllowedButtons[];
showProgress?: boolean;
progressText?: string;
nextBtnText?: string;
prevBtnText?: string;
doneBtnText?: string;
logoUrl?: string;
// Additional callbacks
onHighlightStarted?: DriverHook;
onHighlighted?: DriverHook;
onDeselected?: DriverHook;
onDestroyStarted?: DriverHook;
onDestroyed?: DriverHook;
onNextClick?: DriverHook;
onPrevClick?: DriverHook;
onCloseClick?: DriverHook;
}
These govern the visuals and interactivity of the highlight overlay and popover. For example, overlayOpacity: 0.7
or smoothScroll: true
.