web-tourify
v2.0.0
Published
A lightweight product tour library for Next.js with RTL support, auto-mirroring, and font customization
Maintainers
Readme
Web-Tourify
A lightweight, customizable product tour library for Next.js applications with full RTL (right-to-left) support.
Table of Contents
- Features
- Installation
- Quick Start
- RTL Support
- Custom Card Component
- Tour Configuration
- API Reference
- Programmatic Control
- Styling
- Examples
- TypeScript Support
- License
Features
- RTL Support - Full right-to-left language support with automatic tooltip position mirroring
- Auto-Mirroring - Tooltip positions automatically flip for RTL layouts (left ↔ right)
- Custom Fonts - Configurable fonts for LTR and RTL with automatic Google Fonts loading
- Multiple Tours - Create and manage multiple product tours in a single application
- Route Navigation - Steps can automatically navigate between routes using Next.js router
- Fully Customizable - Custom card components, animations, transitions, and styling
- 12 Tooltip Positions - Flexible positioning with top, bottom, left, right, and corner variants
- Smooth Animations - Powered by Framer Motion for fluid transitions
- TypeScript Ready - Full TypeScript support with exported types
- Lightweight - Minimal bundle size with peer dependencies
Installation
# npm
npm install web-tourify
# pnpm
pnpm add web-tourify
# yarn
yarn add web-tourifyPeer Dependencies
Make sure you have the following peer dependencies installed:
npm install react react-dom next framer-motion @radix-ui/react-portal| Package | Version | |---------|---------| | react | >= 18 | | react-dom | >= 18 | | next | >= 13 | | framer-motion | >= 11 | | @radix-ui/react-portal | >= 1.1.1 |
Quick Start
1. Setup Provider and Component
Wrap your application with WebTourifyProvider and WebTourify in your root layout:
// app/layout.tsx
import { WebTourifyProvider, WebTourify } from "web-tourify";
import { CustomCard } from "@/components/CustomCard";
import { tourSteps } from "@/lib/tour-steps";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<WebTourifyProvider>
<WebTourify
steps={tourSteps}
cardComponent={CustomCard}
>
{children}
</WebTourify>
</WebTourifyProvider>
</body>
</html>
);
}2. Define Your Tour Steps
// lib/tour-steps.tsx
import { Tour } from "web-tourify";
export const tourSteps: Tour[] = [
{
tour: "welcome-tour",
steps: [
{
icon: "👋",
title: "Welcome!",
content: "Let's take a quick tour of the application.",
selector: "#welcome-element",
side: "bottom",
pointerPadding: 10,
pointerRadius: 10,
},
{
icon: "📝",
title: "Create Content",
content: "Click here to create your first item.",
selector: "#create-button",
side: "right",
},
{
icon: "⚙️",
title: "Settings",
content: "Customize your experience in settings.",
selector: "#settings-menu",
side: "left",
nextRoute: "/settings",
},
],
},
];3. Add Target Elements
Add id attributes to elements you want to highlight:
// app/page.tsx
export default function Home() {
return (
<div>
<h1 id="welcome-element">Welcome to Our App</h1>
<button id="create-button">Create New</button>
<nav id="settings-menu">Settings</nav>
</div>
);
}4. Start the Tour
"use client";
import { useWebTourify } from "web-tourify";
export function StartTourButton() {
const { startTour } = useWebTourify();
return (
<button onClick={() => startTour("welcome-tour")}>
Start Tour
</button>
);
}RTL Support
Web-Tourify provides comprehensive RTL (right-to-left) support for internationalized applications.
Global Direction
Set a default direction for all tours:
<WebTourify
steps={tourSteps}
direction="rtl"
cardComponent={CustomCard}
>
{children}
</WebTourify>Per-Tour Direction
Override direction for specific tours:
const tourSteps: Tour[] = [
{
tour: "english-tour",
direction: "ltr",
steps: [/* ... */],
},
{
tour: "arabic-tour",
direction: "rtl",
steps: [/* ... */],
},
];Auto-Mirroring
When RTL is active, horizontal tooltip positions are automatically mirrored:
| Original | Mirrored (RTL) |
|----------|----------------|
| left | right |
| right | left |
| top-left | top-right |
| top-right | top-left |
| bottom-left | bottom-right |
| bottom-right | bottom-left |
| left-top | right-top |
| left-bottom | right-bottom |
| right-top | left-top |
| right-bottom | left-bottom |
Disable auto-mirroring if you want manual control:
<WebTourify autoMirror={false} /* ... */ />Font Configuration
Default fonts are automatically loaded from Google Fonts:
- LTR: DM Sans
- RTL: Noto Kufi Arabic
Customize fonts:
<WebTourify
fontConfig={{
ltr: "'Inter', sans-serif",
rtl: "'Cairo', sans-serif",
loadFonts: false, // Disable automatic Google Fonts injection
}}
/* ... */
/>Custom Card Component
Create a fully custom tooltip component to match your design system:
"use client";
import type { CardComponentProps } from "web-tourify";
export function CustomCard({
step,
currentStep,
totalSteps,
nextStep,
prevStep,
arrow,
direction,
}: CardComponentProps) {
return (
<div
className="bg-white rounded-lg shadow-xl p-6 max-w-sm"
style={{ direction }}
>
{/* Arrow indicator */}
<div className="text-white">{arrow}</div>
{/* Header */}
<div className="flex items-center gap-2 mb-3">
{step.icon && <span className="text-2xl">{step.icon}</span>}
<h3 className="text-lg font-semibold">{step.title}</h3>
</div>
{/* Content */}
<div className="text-gray-600 mb-4">{step.content}</div>
{/* Progress */}
<div className="text-sm text-gray-400 mb-4">
Step {currentStep + 1} of {totalSteps}
</div>
{/* Navigation */}
<div className="flex justify-between gap-2">
<button
onClick={prevStep}
disabled={currentStep === 0}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded disabled:opacity-50"
>
Previous
</button>
<button
onClick={nextStep}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
{currentStep === totalSteps - 1 ? "Finish" : "Next"}
</button>
</div>
</div>
);
}CardComponentProps
| Prop | Type | Description |
|------|------|-------------|
| step | Step | Current step configuration object |
| currentStep | number | Zero-based index of current step |
| totalSteps | number | Total number of steps in the tour |
| nextStep | () => void | Function to advance to next step |
| prevStep | () => void | Function to go to previous step |
| arrow | JSX.Element | Pre-styled arrow SVG pointing to target |
| direction | 'ltr' \| 'rtl' | Current text direction |
Tour Configuration
Tour Object
interface Tour {
tour: string; // Unique tour identifier
steps: Step[]; // Array of step configurations
direction?: Direction; // Optional: 'ltr' | 'rtl'
}Step Object
interface Step {
// Content
icon?: React.ReactNode | string | null; // Icon or emoji
title: string; // Step title
content: React.ReactNode; // Step description/content
selector: string; // CSS selector for target element
// Positioning
side?: Side; // Tooltip position (default: 'bottom')
pointerPadding?: number; // Padding around highlighted element (default: 30)
pointerRadius?: number; // Border radius of highlight (default: 28)
// Navigation
nextRoute?: string; // Route to navigate on "next"
prevRoute?: string; // Route to navigate on "previous"
// Display
showControls?: boolean; // Show default controls (if using default card)
}Side Positions
top-left top top-right
┌─────────┐
left-top │ │ right-top
│ TARGET │
left-bottom │ │ right-bottom
└─────────┘
bottom-left bottom bottom-rightAvailable positions:
top,bottom,left,righttop-left,top-right,bottom-left,bottom-rightleft-top,left-bottom,right-top,right-bottom
API Reference
WebTourifyProvider
Context provider that must wrap your application.
<WebTourifyProvider>
{children}
</WebTourifyProvider>WebTourify
Main component that renders the tour overlay.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| children | React.ReactNode | required | Your application content |
| steps | Tour[] | required | Array of tour configurations |
| cardComponent | React.ComponentType<CardComponentProps> | - | Custom card component |
| direction | 'ltr' \| 'rtl' | 'ltr' | Global text direction |
| autoMirror | boolean | true | Auto-mirror positions for RTL |
| fontConfig | FontConfig | - | Font configuration |
| interact | boolean | false | Allow interaction with highlighted elements |
| shadowRgb | string | '0, 0, 0' | RGB values for overlay shadow |
| shadowOpacity | string | '0.2' | Opacity of overlay shadow |
| cardTransition | Transition | { ease: 'anticipate', duration: 0.6 } | Framer Motion transition |
| showTour | boolean | false | Control visibility externally |
FontConfig
interface FontConfig {
ltr?: string; // Font family for LTR (default: 'DM Sans')
rtl?: string; // Font family for RTL (default: 'Noto Kufi Arabic')
loadFonts?: boolean; // Auto-load Google Fonts (default: true)
}Programmatic Control
Use the useWebTourify hook to control tours from any component:
"use client";
import { useWebTourify } from "web-tourify";
export function TourControls() {
const {
startTour,
closeTour,
setCurrentStep,
currentStep,
currentTour,
currentDirection,
isVisible,
} = useWebTourify();
return (
<div>
{/* Start a specific tour */}
<button onClick={() => startTour("welcome-tour")}>
Start Welcome Tour
</button>
{/* Close the current tour */}
<button onClick={() => closeTour()}>
End Tour
</button>
{/* Jump to a specific step */}
<button onClick={() => setCurrentStep(2)}>
Go to Step 3
</button>
{/* With delay (useful after route changes) */}
<button onClick={() => setCurrentStep(0, 500)}>
Restart with delay
</button>
{/* Access current state */}
<p>Current tour: {currentTour}</p>
<p>Current step: {currentStep}</p>
<p>Direction: {currentDirection}</p>
<p>Visible: {isVisible ? 'Yes' : 'No'}</p>
</div>
);
}Hook Return Values
| Property | Type | Description |
|----------|------|-------------|
| startTour | (tourName: string) => void | Start a tour by name |
| closeTour | () => void | Close the current tour |
| setCurrentStep | (step: number, delay?: number) => void | Jump to a specific step |
| currentTour | string \| null | Name of active tour |
| currentStep | number | Current step index |
| currentDirection | 'ltr' \| 'rtl' | Current text direction |
| isVisible | boolean | Whether tour is visible |
Styling
Tailwind CSS Configuration
If using the default styles, add web-tourify to your Tailwind content configuration:
// tailwind.config.js
module.exports = {
content: [
// ... your paths
'./node_modules/web-tourify/dist/**/*.{js,ts,jsx,tsx}',
],
// ...
}Custom Styling
The tour overlay uses these z-index values:
- Overlay blocker:
z-900 - Pointer/highlight:
z-900 - Card:
z-950
Target element during tour:
position: relativez-index: 990(wheninteract={true})
CSS Custom Properties
Style the arrow in your custom card:
[data-name="web-tourify-arrow"] {
color: white; /* Arrow color inherits from currentColor */
}Examples
Multi-language Tour
const tours: Tour[] = [
{
tour: "english-onboarding",
direction: "ltr",
steps: [
{
title: "Welcome",
content: "Let's get started!",
selector: "#hero",
side: "bottom",
},
],
},
{
tour: "arabic-onboarding",
direction: "rtl",
steps: [
{
title: "مرحبا",
content: "!هيا نبدأ",
selector: "#hero",
side: "bottom",
},
],
},
];Tour with Route Navigation
const tours: Tour[] = [
{
tour: "full-app-tour",
steps: [
{
title: "Dashboard",
content: "This is your main dashboard.",
selector: "#dashboard",
side: "bottom",
nextRoute: "/settings", // Navigate to settings on "next"
},
{
title: "Settings",
content: "Configure your preferences here.",
selector: "#settings-panel",
side: "left",
prevRoute: "/", // Navigate back on "previous"
},
],
},
];Conditional Tour Start
"use client";
import { useEffect } from "react";
import { useWebTourify } from "web-tourify";
export function OnboardingCheck() {
const { startTour } = useWebTourify();
useEffect(() => {
const hasSeenTour = localStorage.getItem("hasSeenTour");
if (!hasSeenTour) {
startTour("welcome-tour");
localStorage.setItem("hasSeenTour", "true");
}
}, [startTour]);
return null;
}TypeScript Support
All types are exported for TypeScript users:
import type {
Tour,
Step,
Direction,
FontConfig,
CardComponentProps,
WebTourifyProps,
WebTourifyContextType,
} from "web-tourify";License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
