{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "guided-tour",
  "title": "Guided Tour",
  "description": "A guided tour component for onboarding users.",
  "dependencies": [
    "@base-ui/react",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://optics.agusmayol.com.ar/r/button.json",
    "https://optics.agusmayol.com.ar/r/popover.json",
    "https://optics.agusmayol.com.ar/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/optics/guided-tour.jsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\";\nimport {\n\tPopover,\n\tPopoverTrigger,\n\tPopoverContent,\n} from \"@/registry/optics/popover\";\nimport { cn } from \"@/registry/optics/lib/utils\";\nimport { Button } from \"@/registry/optics/button\";\nimport { X } from \"lucide-react\";\n\nconst GuidedTourContext = React.createContext({\n\tactiveTour: null,\n\tsetActiveTour: () => {},\n\tcurrentStep: 0,\n\tsetCurrentStep: () => {},\n\tsteps: [],\n\tsetSteps: () => {},\n\ttotalSteps: 0,\n});\n\nfunction GuidedTourProvider({ children = null, ...props }) {\n\tconst [activeTour, setActiveTour] = React.useState(null);\n\tconst [currentStep, setCurrentStep] = React.useState(0);\n\tconst [steps, setSteps] = React.useState([]);\n\tconst [totalSteps, setTotalSteps] = React.useState(0);\n\n\tconst contextValue = React.useMemo(\n\t\t() => ({\n\t\t\tactiveTour,\n\t\t\tsetActiveTour,\n\t\t\tcurrentStep,\n\t\t\tsetCurrentStep,\n\t\t\tsteps,\n\t\t\tsetSteps,\n\t\t\ttotalSteps,\n\t\t\tsetTotalSteps,\n\t\t}),\n\t\t[activeTour, currentStep, steps, totalSteps],\n\t);\n\n\treturn (\n\t\t<GuidedTourContext.Provider value={contextValue} {...props}>\n\t\t\t{children}\n\t\t</GuidedTourContext.Provider>\n\t);\n}\n\nfunction useGuidedTour() {\n\tconst context = React.useContext(GuidedTourContext);\n\tif (!context) {\n\t\tthrow new Error(\"useGuidedTour must be used within GuidedTourProvider\");\n\t}\n\treturn context;\n}\n\nconst GuidedTourOverlay = React.forwardRef(({ className, ...props }, ref) => {\n\tconst { activeTour } = useGuidedTour();\n\n\tif (!activeTour) return null;\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\tclassName={cn(\n\t\t\t\t\"fixed inset-0 z-[100] bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nGuidedTourOverlay.displayName = \"GuidedTourOverlay\";\n\nfunction GuidedTour({ children = null, ...props }) {\n\treturn (\n\t\t<div data-slot=\"guided-tour\" {...props}>\n\t\t\t{children}\n\t\t</div>\n\t);\n}\n\nfunction GuidedTourTrigger({\n\ttourId = \"\",\n\tchildren = null,\n\tclassName = \"\",\n\trender = null,\n\t...props\n}) {\n\tconst { onClick, ...restProps } = props;\n\tconst { setActiveTour, setCurrentStep, setTotalSteps } = useGuidedTour();\n\n\tconst handleClick = (event) => {\n\t\tif (onClick) {\n\t\t\tonClick(event);\n\t\t}\n\n\t\tif (event?.defaultPrevented) return;\n\n\t\tconst tourSteps = document.querySelectorAll(\n\t\t\t`[data-tour=\"${tourId}\"][data-step]`,\n\t\t);\n\t\tconst sortedSteps = Array.from(tourSteps).sort((a, b) => {\n\t\t\tconst stepA = parseInt(a.getAttribute(\"data-step\")) || 0;\n\t\t\tconst stepB = parseInt(b.getAttribute(\"data-step\")) || 0;\n\t\t\treturn stepA - stepB;\n\t\t});\n\n\t\tif (sortedSteps.length > 0) {\n\t\t\tsetTotalSteps(sortedSteps.length);\n\t\t\tsetCurrentStep(0);\n\t\t\tsetActiveTour(tourId);\n\n\t\t\t// Scroll to the first element\n\t\t\tsetTimeout(() => {\n\t\t\t\tsortedSteps[0]?.scrollIntoView({\n\t\t\t\t\tbehavior: \"smooth\",\n\t\t\t\t\tblock: \"center\",\n\t\t\t\t});\n\t\t\t}, 100);\n\t\t}\n\t};\n\n\tconst triggerProps = {\n\t\t\"data-slot\": \"guided-tour-trigger\",\n\t\tonClick: handleClick,\n\t\tclassName: cn(!render && \"cursor-pointer\", className),\n\t\ttype: render ? undefined : \"button\",\n\t\t...restProps,\n\t};\n\n\tif (render && React.isValidElement(render)) {\n\t\treturn React.cloneElement(render, triggerProps);\n\t}\n\n\treturn <Button {...triggerProps}>{children}</Button>;\n}\n\nconst GuidedTourStep = React.forwardRef(\n\t(\n\t\t{\n\t\t\ttourId = \"\",\n\t\t\tstep = 0,\n\t\t\tchildren = null,\n\t\t\tclassName = \"\",\n\t\t\tcontent = null,\n\t\t\t...props\n\t\t},\n\t\tref,\n\t) => {\n\t\tconst { activeTour, currentStep, setActiveTour, setCurrentStep } =\n\t\t\tuseGuidedTour();\n\t\tconst isActive = activeTour === tourId && currentStep === step - 1;\n\t\tconst elementRef = React.useRef(null);\n\t\tconst [open, setOpen] = React.useState(false);\n\n\t\tReact.useEffect(() => {\n\t\t\tif (isActive && elementRef.current) {\n\t\t\t\tsetOpen(true);\n\t\t\t\t// Scroll to the active element\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\telementRef.current?.scrollIntoView({\n\t\t\t\t\t\tbehavior: \"smooth\",\n\t\t\t\t\t\tblock: \"center\",\n\t\t\t\t\t});\n\t\t\t\t}, 100);\n\t\t\t} else {\n\t\t\t\tsetOpen(false);\n\t\t\t}\n\t\t}, [isActive]);\n\n\t\tconst handleOpenChange = (newOpen) => {\n\t\t\tsetOpen(newOpen);\n\t\t\t// If the popover is closed by user interaction (click outside), deactivate the tour\n\t\t\tif (!newOpen && isActive && activeTour === tourId) {\n\t\t\t\t// Capture current values to check after timeout\n\t\t\t\tconst currentTourId = activeTour;\n\t\t\t\tconst currentStepValue = currentStep;\n\t\t\t\t// Use a small timeout to allow step transitions to complete\n\t\t\t\t// If we're transitioning to another step, the tour will remain active\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t// Only deactivate if the tour is still active and we're still on this step\n\t\t\t\t\t// This means the user closed the popover manually, not by navigating\n\t\t\t\t\tif (\n\t\t\t\t\t\tactiveTour === currentTourId &&\n\t\t\t\t\t\tcurrentStep === currentStepValue\n\t\t\t\t\t) {\n\t\t\t\t\t\tsetActiveTour(null);\n\t\t\t\t\t\tsetCurrentStep(0);\n\t\t\t\t\t}\n\t\t\t\t}, 100);\n\t\t\t}\n\t\t};\n\n\t\tconst clonedElement = React.cloneElement(children, {\n\t\t\tref: (node) => {\n\t\t\t\telementRef.current = node;\n\t\t\t\tif (typeof children.ref === \"function\") {\n\t\t\t\t\tchildren.ref(node);\n\t\t\t\t} else if (ref) {\n\t\t\t\t\tif (typeof ref === \"function\") {\n\t\t\t\t\t\tref(node);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tref.current = node;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"data-tour\": tourId,\n\t\t\t\"data-step\": step,\n\t\t\tclassName: cn(\n\t\t\t\tclassName,\n\t\t\t\tisActive && \"relative z-[101]\",\n\t\t\t\tchildren.props?.className,\n\t\t\t),\n\t\t\t...props,\n\t\t});\n\t\tconst isNativeButton =\n\t\t\ttypeof clonedElement.type === \"string\" &&\n\t\t\tclonedElement.type.toLowerCase() === \"button\";\n\n\t\tif (!isActive || !content) {\n\t\t\treturn clonedElement;\n\t\t}\n\n\t\treturn (\n\t\t\t<Popover\n\t\t\t\topen={open}\n\t\t\t\tonOpenChange={(nextOpen) => handleOpenChange(nextOpen)}\n\t\t\t>\n\t\t\t\t<PopoverTrigger nativeButton={isNativeButton} render={clonedElement} />\n\t\t\t\t<GuidedTourStepContent>{content}</GuidedTourStepContent>\n\t\t\t</Popover>\n\t\t);\n\t},\n);\nGuidedTourStep.displayName = \"GuidedTourStep\";\n\nfunction GuidedTourStepContent({ children }) {\n\tconst { currentStep, setCurrentStep, setActiveTour, totalSteps } =\n\t\tuseGuidedTour();\n\n\tconst handleNext = () => {\n\t\tif (currentStep < totalSteps - 1) {\n\t\t\tsetCurrentStep(currentStep + 1);\n\t\t} else {\n\t\t\thandleFinish();\n\t\t}\n\t};\n\n\tconst handleBack = () => {\n\t\tif (currentStep > 0) {\n\t\t\tsetCurrentStep(currentStep - 1);\n\t\t}\n\t};\n\n\tconst handleSkip = () => {\n\t\tsetActiveTour(null);\n\t\tsetCurrentStep(0);\n\t};\n\n\tconst handleFinish = () => {\n\t\tsetActiveTour(null);\n\t\tsetCurrentStep(0);\n\t};\n\n\tconst isLastStep = currentStep === totalSteps - 1;\n\tconst isFirstStep = currentStep === 0;\n\n\treturn (\n\t\t<GuidedTourContent\n\t\t\tonSkip={handleSkip}\n\t\t\tonNext={handleNext}\n\t\t\tonBack={handleBack}\n\t\t\tonFinish={handleFinish}\n\t\t\tisLastStep={isLastStep}\n\t\t\tisFirstStep={isFirstStep}\n\t\t>\n\t\t\t{children}\n\t\t</GuidedTourContent>\n\t);\n}\n\nfunction GuidedTourContent({\n\tchildren = null,\n\tclassName = \"\",\n\tonSkip = undefined,\n\tonNext = undefined,\n\tonBack = undefined,\n\tonFinish = undefined,\n\tisLastStep = false,\n\tisFirstStep = false,\n\talign = \"start\",\n\talignOffset = 0,\n\tside = \"bottom\",\n\tsideOffset = 8,\n\t...props\n}) {\n\tconst { currentStep, totalSteps } = useGuidedTour();\n\n\treturn (\n\t\t<PopoverContent\n\t\t\talign={align}\n\t\t\talignOffset={alignOffset}\n\t\t\tside={side}\n\t\t\tsideOffset={sideOffset}\n\t\t\tpositionerClassName=\"z-[102]\"\n\t\t\tclassName={cn(\n\t\t\t\t\"w-80 rounded-lg border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tdata-slot=\"guided-tour-content\"\n\t\t\t{...props}\n\t\t>\n\t\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t\t<div className=\"flex items-start justify-between gap-2\">\n\t\t\t\t\t<div className=\"flex-1\">{children}</div>\n\t\t\t\t\t{onSkip && (\n\t\t\t\t\t\t<PopoverPrimitive.Close\n\t\t\t\t\t\t\trender={\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tonClick={onSkip}\n\t\t\t\t\t\t\t\t\tclassName=\"rounded-sm opacity-70 hover:opacity-100 transition-opacity p-1 -mt-1 -mr-1\"\n\t\t\t\t\t\t\t\t\taria-label=\"Close\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<X className=\"h-4 w-4\" />\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\n\t\t\t\t<div className=\"flex items-center justify-between gap-2\">\n\t\t\t\t\t<div className=\"text-xs text-muted-foreground\">\n\t\t\t\t\t\tStep {currentStep + 1} of {totalSteps}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"flex items-center gap-2\">\n\t\t\t\t\t\t{onSkip && (\n\t\t\t\t\t\t\t<Button variant=\"ghost\" size=\"sm\" onClick={onSkip} type=\"button\">\n\t\t\t\t\t\t\t\tSkip\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{onBack && !isFirstStep && (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"outline\"\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tonClick={onBack}\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tBack\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{isLastStep ? (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tonClick={onFinish}\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tFinish\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\tonClick={onNext}\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\tNext\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</PopoverContent>\n\t);\n}\n\n// GuidedTourPopover is now deprecated - use GuidedTourStep with content prop instead\nfunction GuidedTourPopover({ children = null, ...props }) {\n\tconst { activeTour, currentStep, setCurrentStep, setActiveTour, totalSteps } =\n\t\tuseGuidedTour();\n\tconst [open, setOpen] = React.useState(false);\n\tconst [currentElement, setCurrentElement] = React.useState(null);\n\n\tReact.useEffect(() => {\n\t\tif (activeTour) {\n\t\t\tconst element = document.querySelector(\n\t\t\t\t`[data-tour=\"${activeTour}\"][data-step=\"${currentStep + 1}\"]`,\n\t\t\t);\n\t\t\tif (element) {\n\t\t\t\tsetCurrentElement(element);\n\t\t\t\tsetOpen(true);\n\t\t\t\t// Scroll to the element\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\telement.scrollIntoView({\n\t\t\t\t\t\tbehavior: \"smooth\",\n\t\t\t\t\t\tblock: \"center\",\n\t\t\t\t\t});\n\t\t\t\t}, 100);\n\t\t\t} else {\n\t\t\t\tsetOpen(false);\n\t\t\t\tsetCurrentElement(null);\n\t\t\t}\n\t\t} else {\n\t\t\tsetOpen(false);\n\t\t\tsetCurrentElement(null);\n\t\t}\n\t}, [activeTour, currentStep]);\n\n\tconst handleNext = () => {\n\t\tif (currentStep < totalSteps - 1) {\n\t\t\tsetCurrentStep(currentStep + 1);\n\t\t} else {\n\t\t\thandleFinish();\n\t\t}\n\t};\n\n\tconst handleBack = () => {\n\t\tif (currentStep > 0) {\n\t\t\tsetCurrentStep(currentStep - 1);\n\t\t}\n\t};\n\n\tconst handleSkip = () => {\n\t\tsetActiveTour(null);\n\t\tsetCurrentStep(0);\n\t\tsetOpen(false);\n\t\tsetCurrentElement(null);\n\t};\n\n\tconst handleFinish = () => {\n\t\tsetActiveTour(null);\n\t\tsetCurrentStep(0);\n\t\tsetOpen(false);\n\t\tsetCurrentElement(null);\n\t};\n\n\tconst isLastStep = currentStep === totalSteps - 1;\n\tconst isFirstStep = currentStep === 0;\n\n\tif (!activeTour || !open || !currentElement) return null;\n\n\treturn (\n\t\t<Popover\n\t\t\topen={open}\n\t\t\tonOpenChange={(nextOpen) => setOpen(nextOpen)}\n\t\t\t{...props}\n\t\t>\n\t\t\t<PopoverTrigger\n\t\t\t\tnativeButton={false}\n\t\t\t\trender={\n\t\t\t\t\t<div\n\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tleft: currentElement.getBoundingClientRect().left,\n\t\t\t\t\t\t\ttop: currentElement.getBoundingClientRect().top,\n\t\t\t\t\t\t\twidth: currentElement.getBoundingClientRect().width,\n\t\t\t\t\t\t\theight: currentElement.getBoundingClientRect().height,\n\t\t\t\t\t\t\tpointerEvents: \"none\",\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t}\n\t\t\t/>\n\t\t\t<GuidedTourContent\n\t\t\t\tonSkip={handleSkip}\n\t\t\t\tonNext={handleNext}\n\t\t\t\tonBack={handleBack}\n\t\t\t\tonFinish={handleFinish}\n\t\t\t\tisLastStep={isLastStep}\n\t\t\t\tisFirstStep={isFirstStep}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</GuidedTourContent>\n\t\t</Popover>\n\t);\n}\n\nGuidedTourProvider.displayName = \"GuidedTourProvider\";\nGuidedTour.displayName = \"GuidedTour\";\nGuidedTourTrigger.displayName = \"GuidedTourTrigger\";\n// GuidedTourStep already has displayName set in its forwardRef\nGuidedTourOverlay.displayName = \"GuidedTourOverlay\";\nGuidedTourContent.displayName = \"GuidedTourContent\";\nGuidedTourPopover.displayName = \"GuidedTourPopover\";\n\nexport {\n\tGuidedTourProvider,\n\tGuidedTour,\n\tGuidedTourTrigger,\n\tGuidedTourStep,\n\tGuidedTourOverlay,\n\tGuidedTourContent,\n\tGuidedTourPopover,\n\tuseGuidedTour,\n};\n",
      "type": "registry:component",
      "target": "components/optics/guided-tour.jsx"
    }
  ],
  "type": "registry:component"
}