UNPKG

rn-sherpa

Version:

A lightweight, flexible React Native library for creating powerful, step-by-step guided product tours with smart positioning and animations

585 lines (460 loc) 15.3 kB
# rn-sherpa A lightweight, flexible, and dependency-free library for creating powerful, step-by-step guided product tours for your React Native applications. Inspired by the amazing Driver.js, this library lets you highlight UI components to guide your users, improve the onboarding process, and showcase new features in an elegant and simple way. ## Features - **Lightweight & Dependency-Free**: Built from the ground up with only React and React Native - **Declarative API**: Define your tour steps in a simple array of objects - **Fully Customizable**: Full control over the look and feel of the popover and overlay - **Smooth Animations**: Support for fluid transitions between steps with Reanimated - **Smart Positioning**: Automatically adjusts popover position to stay visible on screen - **Scroll-to-View**: Automatically scrolls elements into view when tour steps change - **Written in TypeScript**: Type-safe with autocompletion for a better developer experience ## Installation ```bash npm install rn-sherpa react-native-reanimated # or yarn add rn-sherpa react-native-reanimated ``` ### Setup Reanimated Add the Reanimated plugin to your `babel.config.js`: ```js module.exports = { presets: ['module:@react-native/babel-preset'], plugins: ['react-native-reanimated/plugin'], }; ``` > **Note**: The Reanimated plugin must be listed **last** in the plugins array. ## Quick Start Here's a simple example to get you started: ```tsx import React from 'react'; import { View, Text, TouchableOpacity, ScrollView } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { TourProvider, TourOverlay, useTour, useStepRef, useAutoScroll, type TourConfig, } from 'rn-sherpa'; function App() { const tourConfig: TourConfig = { steps: [ { id: 'welcome', title: 'Welcome! 🎉', text: 'Let me show you around this app.', popoverPosition: 'center', }, { id: 'header', title: 'App Header', text: 'This is your main navigation area.', popoverPosition: 'bottom', padding: 12, borderRadius: 12, }, { id: 'button', title: 'Action Button', text: 'Tap this button to perform an action.', popoverPosition: 'bottom', }, ], showButtons: true, showProgress: true, allowClose: true, animationDuration: 400, overlayColor: 'rgba(0, 0, 0, 0.75)', }; return ( <TourProvider config={tourConfig}> <SafeAreaProvider> <YourApp /> <TourOverlay config={tourConfig} /> </SafeAreaProvider> </TourProvider> ); } function YourApp() { const tour = useTour(); const scrollViewRef = React.useRef<ScrollView>(null); // Create refs for tour steps const headerRef = useStepRef<View>(); const buttonRef = useStepRef<View>(); // Automatically scroll to elements during tour useAutoScroll(scrollViewRef); // Assign refs to tour steps React.useEffect(() => { if (tour.currentStep) { switch (tour.currentStep.id) { case 'header': tour.currentStep.ref = headerRef; break; case 'button': tour.currentStep.ref = buttonRef; break; } } }, [tour.currentStep, headerRef, buttonRef]); return ( <View style={{ flex: 1 }}> <ScrollView ref={scrollViewRef}> <View ref={headerRef} style={{ padding: 20, backgroundColor: '#007AFF' }}> <Text style={{ color: 'white', fontSize: 24 }}>My App</Text> </View> <TouchableOpacity onPress={tour.start} style={{ padding: 16, backgroundColor: '#28a745', margin: 20 }} > <Text style={{ color: 'white' }}>Start Tour</Text> </TouchableOpacity> <TouchableOpacity ref={buttonRef} style={{ padding: 16, backgroundColor: '#007AFF', margin: 20 }} > <Text style={{ color: 'white' }}>Important Button</Text> </TouchableOpacity> </ScrollView> </View> ); } ``` ## API Reference ### TourProvider Wrap your app with `TourProvider` to enable tour functionality. ```tsx <TourProvider config={tourConfig}> {/* Your app */} </TourProvider> ``` ### TourConfig Configuration object for the tour: ```tsx interface TourConfig { // Required steps: TourStep[]; // UI Options showButtons?: boolean; // Show navigation buttons (default: true) showProgress?: boolean; // Show "X of Y" progress indicator (default: true) allowClose?: boolean; // Allow closing tour with X button (default: true) // Animation animationDuration?: number; // Animation duration in ms (default: 300) // Styling overlayColor?: string; // Overlay color (default: 'rgba(0, 0, 0, 0.75)') popoverStyle?: ViewStyle; // Custom styles for popover overlayStyle?: ViewStyle; // Custom styles for overlay // Callbacks onStart?: () => void; // Called when tour starts onComplete?: () => void; // Called when tour completes normally onSkip?: () => void; // Called when tour is closed/skipped } ``` ### TourStep Configuration for individual tour steps: ```tsx interface TourStep { id: string; text: string; title?: string; ref?: React.RefObject<any>; popoverContent?: ReactNode; popoverPosition?: 'top' | 'bottom' | 'left' | 'right' | 'center'; onShow?: () => void; onHide?: () => void; padding?: number; borderRadius?: number; } ``` ### useTour Hook Access tour controls and state: ```tsx const { currentStepIndex, totalSteps, isActive, start, stop, next, previous, goToStep, currentStep, } = useTour(); ``` ### useStepRef Hook Create refs for components to highlight: ```tsx const myComponentRef = useStepRef<View>(); <View ref={myComponentRef}> {/* Component to highlight */} </View> ``` ### useAutoScroll Hook Automatically scrolls to tour elements in ScrollView: ```tsx const scrollViewRef = useRef<ScrollView>(null); // Basic usage useAutoScroll(scrollViewRef); // With options useAutoScroll(scrollViewRef, { topPadding: 150, // Padding from top (default: 100) animated: true, // Animate scroll (default: true) enabled: true, // Enable/disable (default: true) }); <ScrollView ref={scrollViewRef}> {/* Your content */} </ScrollView> ``` ## Key Features Explained ### Smart Positioning The library automatically adjusts popover positions to ensure they're always visible on screen. When you specify a `popoverPosition`, the library: 1. **Checks available space** in all directions 2. **Automatically switches position** if there's insufficient space 3. **Maintains proper spacing** from screen edges Example: ```tsx { id: 'bottom-button', title: 'Settings', text: 'Access your settings here.', popoverPosition: 'bottom', // Will auto-switch to 'top' if near bottom of screen } ``` **How it works:** - If you choose `bottom` but there's less than 240px space below → switches to `top` - If you choose `top` but there's less than 240px space above → switches to `bottom` - Same logic applies for `left` ↔ `right` - Animations automatically match the actual calculated position ### Auto-Scroll to Elements For scrollable content, you can implement auto-scrolling to ensure tour elements are visible: ```tsx function YourApp() { const tour = useTour(); const scrollViewRef = React.useRef<ScrollView>(null); // Auto-scroll when tour step changes React.useEffect(() => { if (tour.isActive && tour.currentStep?.ref?.current && scrollViewRef.current) { const ref = tour.currentStep.ref.current; ref.measureLayout( scrollViewRef.current as any, (_x: number, y: number, _width: number, height: number) => { scrollViewRef.current?.scrollTo({ y: Math.max(0, y - 100), // 100px padding from top animated: true, }); }, (error) => console.log('Measurement failed:', error) ); } }, [tour.isActive, tour.currentStep, tour.currentStepIndex]); return ( <ScrollView ref={scrollViewRef}> {/* Your content */} </ScrollView> ); } ``` **Best Practices:** - Always add padding when scrolling (e.g., `y - 100`) to avoid elements at screen edges - Use `animated: true` for smooth scrolling transitions - The library includes measurement retry logic to handle elements that need time to render ### Spotlight Customization Customize the spotlight cutout around highlighted elements: ```tsx { id: 'custom-spotlight', title: 'Custom Highlight', text: 'Notice the rounded corners and extra padding.', padding: 16, // Extra space around element (default: 8) borderRadius: 20, // Corner radius for spotlight (default: 8) } ``` ## Advanced Usage ### Custom Popover Content ```tsx const tourConfig: TourConfig = { steps: [ { id: 'custom', title: 'Custom Content', text: 'This won\'t be shown', popoverContent: ( <View> <Text>Your custom JSX content here!</Text> </View> ), }, ], }; ``` ### Programmatic Control ```tsx function MyComponent() { const tour = useTour(); return ( <View> <Button title="Start Tour" onPress={tour.start} /> <Button title="Stop Tour" onPress={tour.stop} /> <Button title="Next Step" onPress={tour.next} /> <Button title="Previous Step" onPress={tour.previous} /> <Button title="Go to Step 3" onPress={() => tour.goToStep(2)} /> </View> ); } ``` ### Step Callbacks ```tsx const tourConfig: TourConfig = { steps: [ { id: 'step1', title: 'Step 1', text: 'First step', onShow: () => console.log('Step 1 shown'), onHide: () => console.log('Step 1 hidden'), }, ], onStart: () => console.log('Tour started'), onComplete: () => console.log('Tour completed'), onSkip: () => console.log('Tour skipped'), }; ``` ### Animation Customization The library uses `react-native-reanimated` for smooth, performant animations: ```tsx const tourConfig: TourConfig = { steps: [...], animationDuration: 500, // Customize animation speed (default: 300ms) }; ``` **Built-in Animations:** - **Overlay**: Fade in/out with spotlight cutout animation - **Popover**: Position-aware slide animations - `top` position → Slides in from top - `bottom` position → Slides in from bottom - `left` position → Slides in from left - `right` position → Slides in from right - `center` position → Fades in - **Scale effect**: Subtle scale animation (0.91.0) on popovers The animations automatically adapt to the actual calculated position, so if smart positioning switches from `bottom` to `top`, the animation will also switch to match. ## Troubleshooting ### Popover appears off-screen **Solution:** The library includes smart positioning that automatically adjusts placement. If this still happens: 1. Ensure elements have proper dimensions when measured 2. Add a small delay before starting the tour to allow layout to complete: ```tsx setTimeout(() => tour.start(), 100); ``` ### Element not highlighting correctly **Solution:** Elements need to be measured before highlighting. The library includes automatic retry logic, but you can help by: 1. Ensuring the element is rendered before the tour step activates 2. Using `onShow` callback to verify the element is ready: ```tsx { id: 'step1', title: 'Step 1', text: 'Content', onShow: () => console.log('Step showing - element should be rendered'), } ``` ### Scrollable content: Element not scrolling into view **Solution:** Implement the auto-scroll pattern shown in the "Auto-Scroll to Elements" section above. The library handles measurement timing, but you need to provide the scroll logic. ### Animation issues **Problem:** Animations stuttering or not working **Solution:** 1. Ensure `react-native-reanimated` is properly installed 2. Verify babel plugin is added and listed **last** in `babel.config.js` 3. Rebuild your app after adding the Reanimated plugin 4. For iOS: run `pod install` in the ios folder ### TypeScript errors **Problem:** Type errors with refs or config **Solution:** 1. Import types from the library: ```tsx import type { TourConfig, TourStep } from 'rn-sherpa'; ``` 2. Use generic type parameters with `useStepRef`: ```tsx const myRef = useStepRef<View>(); ``` ## Best Practices ### 1. Tour Step Organization Structure your tour steps from top to bottom, following the natural reading order: ```tsx const tourConfig: TourConfig = { steps: [ { id: 'welcome', popoverPosition: 'center' }, // Start with welcome { id: 'header', popoverPosition: 'bottom' }, // Then top elements { id: 'content', popoverPosition: 'top' }, // Middle elements { id: 'footer', popoverPosition: 'top' }, // Bottom elements ], }; ``` ### 2. Position Selection Choose positions that make sense for each element's location: - **Top elements** (header, navbar): Use `popoverPosition: 'bottom'` - **Bottom elements** (footer, tabs): Use `popoverPosition: 'top'` - **Side elements**: Use `left` or `right` based on screen position - **Introductory/concluding steps**: Use `center` for full-screen focus The smart positioning system will auto-adjust if needed! ### 3. Implement Scroll-to-View Always implement scroll-to-view for scrollable content to ensure great UX: ```tsx React.useEffect(() => { if (tour.isActive && tour.currentStep?.ref?.current && scrollViewRef.current) { // Scroll implementation here } }, [tour.isActive, tour.currentStep, tour.currentStepIndex]); ``` ### 4. Use Callbacks Wisely Leverage callbacks for analytics, state management, or triggering actions: ```tsx const tourConfig: TourConfig = { steps: [ { id: 'feature', title: 'New Feature', text: 'Check this out!', onShow: () => { // Track step view analytics.track('tour_step_viewed', { step: 'feature' }); }, }, ], onComplete: () => { // Mark tour as completed AsyncStorage.setItem('tour_completed', 'true'); }, }; ``` ### 5. Customize Spotlight for Context Adjust padding and border radius based on the element type: ```tsx // Large padding for small buttons { id: 'small-icon', padding: 16, borderRadius: 20 } // Minimal padding for large cards { id: 'card', padding: 8, borderRadius: 12 } ``` ### 6. SafeAreaProvider Integration Always wrap your app with `SafeAreaProvider` to handle notches and safe areas correctly: ```tsx import { SafeAreaProvider } from 'react-native-safe-area-context'; <TourProvider config={tourConfig}> <SafeAreaProvider> <YourApp /> </SafeAreaProvider> <TourOverlay config={tourConfig} /> </TourProvider> ``` ### 7. Test on Multiple Devices The smart positioning system works across different screen sizes, but always test: - Small screens (iPhone SE) - Large screens (iPad, large Android phones) - Different orientations (if supported) ## Examples Check out the [example app](../../apps/example) for a complete working demo. ## License MIT ## Contributing Contributions are welcome! Please feel free to submit a Pull Request.