@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
Install the package via npm or yarn:
npm install @sammy-labs/walkthroughs
yarn add @sammy-labs/walkthroughs
This will give you access to:
WalkthroughProvider
(the React provider)
useWalkthrough
(the React hook)
- Various type definitions (e.g.,
FlowError
, WalkthroughResponse
, etc.)
Ensure you also import the CSS file when needed:
@import "@sammy-labs/walkthroughs/dist/index.css";
Or in a Next.js / Plasmo environment:
import "@sammy-labs/walkthroughs/dist/index.css";
Core Concepts
Quick Start
Below is a minimal usage example:
-
Wrap your application:
import React from "react";
import { WalkthroughProvider } from "@sammy-labs/walkthroughs";
import "@sammy-labs/walkthroughs/dist/index.css";
function MyApp({ Component, pageProps }) {
const sammyToken = "YOUR_JWT_TOKEN";
return (
<WalkthroughProvider
token={sammyToken}
baseUrl="https://api.your-sammy-instance.com"
driverConfig={{ overlayOpacity: 0.7 }}
config={{ debug: true }}
>
<Component {...pageProps} />
</WalkthroughProvider>
);
}
export default MyApp;
-
Use the Hook in any component:
import React from "react";
import { useWalkthrough } from "@sammy-labs/walkthroughs";
export function StartButton() {
const { startWithId, isActive, stop } = useWalkthrough();
const handleStart = async () => {
const success = await startWithId("12345");
if (!success) {
alert("Failed to start Sammy walkthrough.");
}
};
return (
<div>
<button onClick={handleStart}>Start Walkthrough #12345</button>
{isActive() && <button onClick={stop}>Stop Walkthrough</button>}
</div>
);
}
That's enough to get you started! The library will automatically fetch data for that flow ID, highlight elements, show popovers, and proceed step by step.
Usage Examples
Next.js Integration
Below is a typical Next.js integration, referencing your environment variables to generate the Sammy token.
"use client";
import React, { useState, useEffect } from "react";
import { WalkthroughProvider } from "@sammy-labs/walkthroughs";
export default function SammyWalkthroughProvider({ children }) {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
async function fetchToken() {
const resp = await fetch("/api/sammy-auth");
const data = await resp.json();
setToken(data.token);
}
fetchToken();
}, []);
if (!token) {
return <div>Loading walkthrough token...</div>;
}
return (
<WalkthroughProvider
token={token}
baseUrl={process.env.NEXT_PUBLIC_SAMMY_BASE_URL}
onTokenExpired={() => {
console.log("Sammy token expired, re-fetching");
}}
onError={(err) => console.warn("Walkthrough error:", err)}
driverConfig={{ overlayOpacity: 0.6 }}
config={{ debug: false, askInput: true }}
>
{children}
</WalkthroughProvider>
);
}
2) Wrap your app or layout:
import SammyWalkthroughProvider from "./providers/SammyWalkthroughProvider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<SammyWalkthroughProvider>{children}</SammyWalkthroughProvider>
</body>
</html>
);
}
3) Use the Hook in a client component:
"use client";
import React from "react";
import { useWalkthrough } from "@sammy-labs/walkthroughs";
export default function Dashboard() {
const { startWithId, stop, isActive } = useWalkthrough();
return (
<div>
<button onClick={() => startWithId("8750")}>Show Onboarding Tips</button>
{isActive() && <button onClick={stop}>Stop</button>}
</div>
);
}
API Reference
WalkthroughProvider
A React provider that must wrap any area that uses the library.
Props
token | string | null | null | A short-lived JWT token used to fetch data from the Sammy API. If omitted, only local flows (startWithData() ) will work. |
baseUrl | string | "http://localhost:8000" | The base URL for Sammy's endpoints. |
onTokenExpired | () => void | none | Called when a 401 occurs, letting you re-fetch a new token. |
onError | (error: FlowError) => void | none | Called when any error occurs (element not found, API failure, etc.). |
driverConfig | Partial<DriverConfig> | { ... } | Fine-grained driver settings (overlay color, popover offsets, etc.) |
config | Partial<WalkthroughConfig> | { ... } | Global config options (debug, domain override, fallback settings, etc.). |
logoUrl | string | "" | An optional logo to display in the popovers. |
children | ReactNode | required | Your app or subtree. |
Example:
<WalkthroughProvider
token="..."
baseUrl="https://my-sammy-server.com"
onTokenExpired={() => console.log("Token expired, re-auth!")}
onError={(e) => console.error("Walkthrough error:", e)}
driverConfig={{ overlayOpacity: 0.7 }}
config={{ debug: true, askInput: false }}
>
<App />
</WalkthroughProvider>
useWalkthrough(options?)
Hook that returns convenience methods and the current walkthrough state.
Signature:
function useWalkthrough(options?: {
checkQueryOnMount?: boolean;
onError?: (error: FlowError) => void;
waitTime?: number;
driverConfig?: Partial<DriverConfig>;
config?: Partial<WalkthroughConfig>;
disableRedirects?: boolean;
autoStartPendingWalkthrough?: boolean;
fallbackTimeout?: number;
}): {
startWithId(flowId: string | number): Promise<boolean>;
startWithData(
data: WalkthroughResponse | WalkthroughSearchResult | any
): Promise<boolean>;
stop(): boolean;
isActive(): boolean;
configure(driverConfig: Partial<DriverConfig>): void;
configureGlobal(globalConfig: Partial<WalkthroughConfig>): void;
state: {
isTokenValid: boolean;
isLoading: boolean;
error: FlowError | null;
token: string | null;
baseUrl: string;
isActive: boolean;
config: WalkthroughConfig;
};
};
Options
-
checkQueryOnMount?: boolean
If true
, automatically checks the URL query parameters (default key sammy_flow_id
) on mount to see if a walkthrough should be started.
-
onError?: (error: FlowError) => void
Local error handler specifically for this hook usage. Merged with or overrides the provider-level onError
.
-
waitTime?: number
Milliseconds to wait before automatically starting a walkthrough from query parameters.
-
driverConfig?: Partial<DriverConfig>
Additional driver config merges on top of the provider config.
-
config?: Partial<WalkthroughConfig>
Additional global config merges on top of the provider config.
-
disableRedirects?: boolean
If true
, will not auto-redirect the user if the recorded URL in the walkthrough data doesn't match the current page.
-
autoStartPendingWalkthrough?: boolean
If true
, automatically attempts to start any pending walkthrough data that was stored from a previous page (useful in multi-page flows).
-
fallbackTimeout?: number
Maximum time in milliseconds to wait for DOM elements to be found before using fallback elements. Defaults to 10000 (10s). This is deliberately separate from the main config object to avoid React re-render issues when used within effects.
Returned Methods
-
startWithId(flowId)
Fetches a walkthrough from the Sammy API using flowId
and starts it.
Returns true
if successful, false
if not.
-
startWithData(data)
Starts a walkthrough using pre-fetched or user-provided data.
- Accepts a
WalkthroughResponse
, or older shapes like search results.
- Returns
true
if success, false
otherwise.
-
stop()
Stops any active walkthrough, removing highlights/overlays.
-
isActive()
Returns true
if a walkthrough is currently in progress.
-
configure(driverConfig)
Dynamically update the driver config.
-
configureGlobal(globalConfig)
Dynamically update the global config.
Returned State
isTokenValid: boolean
Whether token is valid from last check (internal usage).
isLoading: boolean
Whether the library is currently fetching data.
error: FlowError \| null
Error from the last attempt.
token: string \| null
The token being used from the provider.
baseUrl: string
The base API URL in use.
isActive: boolean
Shortcut for isActive()
.
config: WalkthroughConfig
The merged global config object.
Global Configuration (WalkthroughConfig
)
export type WalkthroughConfig = {
flowIdQueryParam: string;
waitTimeAfterLoadMs: number;
maxElementFindAttempts: number;
elementFindTimeoutMs: number;
domStabilityMs: number;
maxDomStabilityWaitMs: number;
debug: boolean;
apiBaseUrl: string;
logoUrl: string;
imageBaseUrl: string;
overrideDomainUrl: string;
askInput: boolean;
enableLogging: boolean;
};
Common fields:
debug
: set to true
to see console logs about fallback elements, element searches, etc.
overrideDomainUrl
: if the recorded steps have different domain references than your current domain, you can override them.
Example:
<WalkthroughProvider
config={{
debug: true,
maxElementFindAttempts: 3,
elementFindTimeoutMs: 10000,
overrideDomainUrl: "https://demo.deel.com",
}}
>
<App />
</WalkthroughProvider>
Example with fallbackTimeout:
function WalkthroughButton() {
const { startWithId } = useWalkthrough({
fallbackTimeout: 15000,
config: {
debug: true,
overrideDomainUrl: "https://demo.deel.com",
},
});
return (
<button onClick={() => startWithId("12345")}>Start Walkthrough</button>
);
}
Driver Configuration (DriverConfig
)
These are more about the highlight overlay and popover.
export interface DriverConfig {
steps?: DriveStep[];
enableLogging?: boolean;
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?: ("next" | "previous" | "close")[];
disableButtons?: ("next" | "previous" | "close")[];
showProgress?: boolean;
progressText?: string;
nextBtnText?: string;
prevBtnText?: string;
doneBtnText?: string;
logoUrl?: string;
onHighlightStarted?: DriverHook;
onHighlighted?: DriverHook;
onDeselected?: DriverHook;
onDestroyStarted?: DriverHook;
onDestroyed?: DriverHook;
onNextClick?: DriverHook;
onPrevClick?: DriverHook;
onCloseClick?: DriverHook;
}
Example:
<WalkthroughProvider
driverConfig={{
overlayOpacity: 0.5,
animate: true,
showButtons: ["next", "previous"],
doneBtnText: "Done!",
}}
>
<App />
</WalkthroughProvider>
Helper Functions (flow-guide utilities)
While normally you only need the Hook, there are some lower-level helpers if you want direct control:
executeApiFlow(flowId, orgId, token, baseUrl, onError)
Fetches the data from Sammy's server and starts the flow.
executeFlowWithData(data, onError, options)
Takes a WalkthroughResponse
(or older format) and starts it.
These are used internally by startWithId
and startWithData
.
Advanced Usage and Tips
Auth Token Management
Your token typically expires after a certain time. If the user is still going through a walkthrough and the token expires, the library calls onTokenExpired()
, giving you a chance to refresh it. Then you can pass the new token back to the provider (e.g., via React state).
<WalkthroughProvider
token={myToken}
onTokenExpired={() => {
}}
>
...
</WalkthroughProvider>
Walkthrough Data Fetching
If you already have the data for a flow (e.g., from server side rendering or some custom server endpoint), you can pass it directly to startWithData()
:
const { startWithData } = useWalkthrough();
await startWithData(myWalkthrough);
No additional fetch calls are needed in that scenario.
Handling Multiple Walkthroughs
Currently, the library supports one active walkthrough at a time. If you try to start a new one while one is running, it will close the previous driver. If you want to queue multiple flows, you can do so sequentially:
const { startWithId, stop } = useWalkthrough();
await startWithId("flowA");
await startWithId("flowB");
FAQ
-
"Why can't it find my element?"
- Ensure your selectors (like
xpath
or attributes) are stable. Turn on debug: true
to see logs. If the element is in a lazy-loaded modal, you may need extra time or increase elementFindTimeoutMs
.
-
"Can I override the domain in the steps?"
- Yes, use
overrideDomainUrl: "https://demo.deel.com"
in your config. The library will reconstruct the final URL for each step using that domain.
-
"How do I customize how long the system waits for elements before creating fallbacks?"
- Use the
fallbackTimeout
parameter directly in the useWalkthrough
hook options. For example: useWalkthrough({ fallbackTimeout: 15000 })
will wait 15 seconds before creating fallback elements. This parameter is separated from the main config object to avoid React re-render issues when used in effects.
-
"How do I pass a custom popup style or embed a custom button?"
- For custom styling, pass a
popoverClass
or an onPopoverRender
callback in driverConfig
. For full control, you can modify the driver callbacks.
-
"Does it work outside of React?"
- The library is React-based. If you need pure JS usage, you might adapt the underlying logic or run the code in a minimal React container.
License
@sammy-labs/walkthroughs
is licensed under the MIT License. See the LICENSE file for more details.
Happy Building with Sammy Walkthroughs!
Need help or have questions? Contact the Sammy team