import { ChevronLeft, ChevronRight } from "lucide-react"; import { createContext, useCallback, useContext, useLayoutEffect, useState, type ReactNode, } from "react"; import CreateScreen from "../screens/util"; import { Button } from "./button"; interface OnboardingStep { id: string; title: ReactNode; description?: ReactNode; logo?: ReactNode; content: ReactNode; defaultCanContinue?: boolean; } interface OnboardingStepControls { canContinue: boolean; onContinue?: () => boolean | void | Promise; } const ConfigureStepContext = createContext< ((controls: OnboardingStepControls) => void) | undefined >(undefined); function useOnboardingStep(controls: OnboardingStepControls) { const configure = useContext(ConfigureStepContext); const { canContinue, onContinue } = controls; if (!configure) { throw new Error("useOnboardingStep must be used inside OnboardingFlow"); } useLayoutEffect(() => { configure({ canContinue, onContinue }); }, [canContinue, configure, onContinue]); } function defaultControls(step: OnboardingStep): OnboardingStepControls { return { canContinue: step.defaultCanContinue ?? true }; } function OnboardingFlow({ steps, onFinish, }: { steps: OnboardingStep[]; onFinish: () => void | Promise; }) { if (steps.length === 0) { throw new Error("OnboardingFlow requires at least one step"); } const [stepIndex, setStepIndex] = useState(0); const [controls, setControls] = useState(() => defaultControls(steps[0]), ); const [pending, setPending] = useState(false); const activeStep = steps[stepIndex]; const configure = useCallback((next: OnboardingStepControls) => { setControls(next); }, []); const showStep = useCallback( (nextIndex: number) => { setControls(defaultControls(steps[nextIndex])); setStepIndex(nextIndex); }, [steps], ); const advance = useCallback(async () => { if (!controls.canContinue || pending) return; setPending(true); try { const result = await controls.onContinue?.(); if (result === false) return; if (stepIndex === steps.length - 1) { await onFinish(); } else { showStep(stepIndex + 1); } } finally { setPending(false); } }, [controls, onFinish, pending, showStep, stepIndex, steps.length]); return (
{activeStep.logo}

{activeStep.title}

{activeStep.description && (
{activeStep.description}
)}
{activeStep.content}
); } export { OnboardingFlow, useOnboardingStep, type OnboardingStep, type OnboardingStepControls, };