78 lines
2.0 KiB
TypeScript
78 lines
2.0 KiB
TypeScript
import React, { createContext, useContext, useState, useCallback, useRef } from 'react';
|
|
import { LayoutRectangle } from 'react-native';
|
|
|
|
export interface CoachStep {
|
|
elementKey: string;
|
|
title: string;
|
|
description: string;
|
|
tooltipSide: 'above' | 'below' | 'left' | 'right';
|
|
}
|
|
|
|
interface CoachMarksState {
|
|
isActive: boolean;
|
|
currentStep: number;
|
|
steps: CoachStep[];
|
|
layouts: Record<string, LayoutRectangle>;
|
|
registerLayout: (key: string, layout: LayoutRectangle) => void;
|
|
startTour: (steps: CoachStep[]) => void;
|
|
next: () => void;
|
|
skip: () => void;
|
|
}
|
|
|
|
const CoachMarksContext = createContext<CoachMarksState | null>(null);
|
|
|
|
export const useCoachMarks = () => {
|
|
const ctx = useContext(CoachMarksContext);
|
|
if (!ctx) throw new Error('useCoachMarks must be within CoachMarksProvider');
|
|
return ctx;
|
|
};
|
|
|
|
export const CoachMarksProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [isActive, setIsActive] = useState(false);
|
|
const [currentStep, setCurrentStep] = useState(0);
|
|
const [steps, setSteps] = useState<CoachStep[]>([]);
|
|
const layouts = useRef<Record<string, LayoutRectangle>>({});
|
|
const [, forceRender] = useState(0);
|
|
|
|
const registerLayout = useCallback((key: string, layout: LayoutRectangle) => {
|
|
layouts.current[key] = layout;
|
|
forceRender(n => n + 1);
|
|
}, []);
|
|
|
|
const startTour = useCallback((newSteps: CoachStep[]) => {
|
|
setSteps(newSteps);
|
|
setCurrentStep(0);
|
|
setIsActive(true);
|
|
}, []);
|
|
|
|
const next = useCallback(() => {
|
|
setCurrentStep(prev => {
|
|
if (prev + 1 >= steps.length) {
|
|
setIsActive(false);
|
|
return 0;
|
|
}
|
|
return prev + 1;
|
|
});
|
|
}, [steps.length]);
|
|
|
|
const skip = useCallback(() => {
|
|
setIsActive(false);
|
|
setCurrentStep(0);
|
|
}, []);
|
|
|
|
return (
|
|
<CoachMarksContext.Provider value={{
|
|
isActive,
|
|
currentStep,
|
|
steps,
|
|
layouts: layouts.current,
|
|
registerLayout,
|
|
startTour,
|
|
next,
|
|
skip,
|
|
}}>
|
|
{children}
|
|
</CoachMarksContext.Provider>
|
|
);
|
|
};
|