{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tour",
  "type": "registry:ui",
  "description": "Step-by-step product tour with spotlight highlighting and progress indicators",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "button"
  ],
  "files": [
    {
      "path": "registry/default/ui/tour.tsx",
      "content": "import * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/components/ui/button'\n\nexport interface TourStep {\n  target: string | HTMLElement | React.RefObject<HTMLElement>\n  title: string\n  description: string\n  placement?: 'top' | 'right' | 'bottom' | 'left' | 'center'\n  spotlightPadding?: number\n  content?: React.ReactNode\n}\n\nexport interface TourProps {\n  steps: TourStep[]\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\n  onComplete?: () => void\n  onSkip?: () => void\n  showSkipButton?: boolean\n  showProgress?: boolean\n}\n\ninterface TourContextValue {\n  currentStep: number\n  totalSteps: number\n  nextStep: () => void\n  prevStep: () => void\n  goToStep: (index: number) => void\n  close: () => void\n  skip: () => void\n}\n\nconst TourContext = React.createContext<TourContextValue | null>(null)\n\nfunction useTour() {\n  const context = React.useContext(TourContext)\n  if (!context) {\n    throw new Error('useTour must be used within a <Tour />')\n  }\n  return context\n}\n\n// Get element from target\nfunction getTargetElement(\n  target: string | HTMLElement | React.RefObject<HTMLElement>\n): HTMLElement | null {\n  if (typeof target === 'string') {\n    return document.querySelector(target)\n  }\n  if (target instanceof HTMLElement) {\n    return target\n  }\n  return target.current\n}\n\n// Calculate popover position\nfunction calculatePosition(\n  targetRect: DOMRect,\n  popoverRect: DOMRect,\n  placement: TourStep['placement'] = 'bottom',\n  padding: number = 8\n): { top: number; left: number; actualPlacement: TourStep['placement'] } {\n  const viewport = {\n    width: window.innerWidth,\n    height: window.innerHeight,\n  }\n\n  let top = 0\n  let left = 0\n  let actualPlacement = placement\n\n  if (placement === 'center') {\n    return {\n      top: viewport.height / 2 - popoverRect.height / 2,\n      left: viewport.width / 2 - popoverRect.width / 2,\n      actualPlacement: 'center',\n    }\n  }\n\n  // Calculate base positions\n  switch (placement) {\n    case 'top':\n      top = targetRect.top - popoverRect.height - padding\n      left = targetRect.left + targetRect.width / 2 - popoverRect.width / 2\n      break\n    case 'bottom':\n      top = targetRect.bottom + padding\n      left = targetRect.left + targetRect.width / 2 - popoverRect.width / 2\n      break\n    case 'left':\n      top = targetRect.top + targetRect.height / 2 - popoverRect.height / 2\n      left = targetRect.left - popoverRect.width - padding\n      break\n    case 'right':\n      top = targetRect.top + targetRect.height / 2 - popoverRect.height / 2\n      left = targetRect.right + padding\n      break\n  }\n\n  // Check if popover fits, flip if necessary\n  if (placement === 'top' && top < 0) {\n    top = targetRect.bottom + padding\n    actualPlacement = 'bottom'\n  } else if (placement === 'bottom' && top + popoverRect.height > viewport.height) {\n    top = targetRect.top - popoverRect.height - padding\n    actualPlacement = 'top'\n  } else if (placement === 'left' && left < 0) {\n    left = targetRect.right + padding\n    actualPlacement = 'right'\n  } else if (placement === 'right' && left + popoverRect.width > viewport.width) {\n    left = targetRect.left - popoverRect.width - padding\n    actualPlacement = 'left'\n  }\n\n  // Keep within viewport bounds\n  left = Math.max(padding, Math.min(left, viewport.width - popoverRect.width - padding))\n  top = Math.max(padding, Math.min(top, viewport.height - popoverRect.height - padding))\n\n  return { top, left, actualPlacement }\n}\n\n// Tour Overlay\ninterface TourOverlayProps {\n  targetRect: DOMRect | null\n  spotlightPadding: number\n}\n\nfunction TourOverlay({ targetRect, spotlightPadding }: TourOverlayProps) {\n  if (!targetRect) {\n    // Center placement - full overlay\n    return (\n      <div className=\"fixed inset-0 z-[9998] bg-black/70\" />\n    )\n  }\n\n  const spotlightRect = {\n    top: targetRect.top - spotlightPadding,\n    left: targetRect.left - spotlightPadding,\n    width: targetRect.width + spotlightPadding * 2,\n    height: targetRect.height + spotlightPadding * 2,\n  }\n\n  return (\n    <div\n      className=\"fixed inset-0 z-[9998]\"\n      style={{\n        background: `\n          linear-gradient(to right, rgba(0,0,0,0.7) ${spotlightRect.left}px, transparent ${spotlightRect.left}px, transparent ${spotlightRect.left + spotlightRect.width}px, rgba(0,0,0,0.7) ${spotlightRect.left + spotlightRect.width}px),\n          linear-gradient(to bottom, rgba(0,0,0,0.7) ${spotlightRect.top}px, transparent ${spotlightRect.top}px, transparent ${spotlightRect.top + spotlightRect.height}px, rgba(0,0,0,0.7) ${spotlightRect.top + spotlightRect.height}px)\n        `,\n        backgroundBlendMode: 'multiply',\n      }}\n    />\n  )\n}\n\n// Tour Popover\ninterface TourPopoverProps {\n  step: TourStep\n  targetRect: DOMRect | null\n  showSkipButton: boolean\n  showProgress: boolean\n}\n\nfunction TourPopover({\n  step,\n  targetRect,\n  showSkipButton,\n  showProgress,\n}: TourPopoverProps) {\n  const { currentStep, totalSteps, nextStep, prevStep, close, skip } = useTour()\n  const popoverRef = React.useRef<HTMLDivElement>(null)\n  const [position, setPosition] = React.useState({ top: 0, left: 0 })\n\n  const isFirst = currentStep === 0\n  const isLast = currentStep === totalSteps - 1\n\n  // Calculate position\n  React.useEffect(() => {\n    if (!popoverRef.current) return\n\n    const popoverRect = popoverRef.current.getBoundingClientRect()\n\n    if (step.placement === 'center' || !targetRect) {\n      setPosition({\n        top: window.innerHeight / 2 - popoverRect.height / 2,\n        left: window.innerWidth / 2 - popoverRect.width / 2,\n      })\n    } else {\n      const pos = calculatePosition(\n        targetRect,\n        popoverRect,\n        step.placement,\n        16\n      )\n      setPosition({ top: pos.top, left: pos.left })\n    }\n  }, [step, targetRect])\n\n  return (\n    <div\n      ref={popoverRef}\n      className={cn(\n        'fixed z-[9999] w-80 border-3 border-foreground bg-popover p-4',\n        'shadow-[8px_8px_0px_hsl(var(--shadow-color))]',\n        'animate-in fade-in-0 zoom-in-95 duration-200'\n      )}\n      style={{ top: position.top, left: position.left }}\n    >\n      {/* Close button */}\n      <button\n        type=\"button\"\n        onClick={close}\n        className={cn(\n          'absolute -right-3 -top-3 border-2 border-foreground bg-background p-1',\n          'shadow-[2px_2px_0px_hsl(var(--shadow-color))]',\n          'hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none',\n          'transition-all duration-150'\n        )}\n      >\n        <X className=\"h-3 w-3 stroke-[3]\" />\n        <span className=\"sr-only\">Close</span>\n      </button>\n\n      {/* Content */}\n      <div className=\"space-y-3\">\n        <h3 className=\"text-base font-bold uppercase tracking-wide\">\n          {step.title}\n        </h3>\n        <p className=\"text-sm text-muted-foreground\">{step.description}</p>\n        {step.content}\n      </div>\n\n      {/* Progress dots */}\n      {showProgress && totalSteps > 1 && (\n        <div className=\"mt-4 flex items-center justify-center gap-1.5\">\n          {Array.from({ length: totalSteps }, (_, i) => (\n            <div\n              key={i}\n              className={cn(\n                'h-2 w-2 border-2 border-foreground transition-all duration-150',\n                i === currentStep ? 'bg-primary scale-110' : 'bg-muted'\n              )}\n            />\n          ))}\n        </div>\n      )}\n\n      {/* Navigation */}\n      <div className=\"mt-4 flex items-center justify-between gap-2\">\n        <div>\n          {showSkipButton && !isLast && (\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              onClick={skip}\n              className=\"text-muted-foreground\"\n            >\n              Skip Tour\n            </Button>\n          )}\n        </div>\n        <div className=\"flex items-center gap-2\">\n          {!isFirst && (\n            <Button variant=\"outline\" size=\"sm\" onClick={prevStep}>\n              Previous\n            </Button>\n          )}\n          <Button size=\"sm\" onClick={isLast ? close : nextStep}>\n            {isLast ? 'Finish' : 'Next'}\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n\n// Main Tour Component\nconst Tour = React.forwardRef<HTMLDivElement, TourProps>(\n  (\n    {\n      steps,\n      open: controlledOpen,\n      onOpenChange,\n      onComplete,\n      onSkip,\n      showSkipButton = true,\n      showProgress = true,\n    },\n    ref\n  ) => {\n    const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false)\n    const [currentStep, setCurrentStep] = React.useState(0)\n    const [targetRect, setTargetRect] = React.useState<DOMRect | null>(null)\n\n    const isControlled = controlledOpen !== undefined\n    const open = isControlled ? controlledOpen : uncontrolledOpen\n\n    const setOpen = React.useCallback(\n      (value: boolean) => {\n        if (!isControlled) {\n          setUncontrolledOpen(value)\n        }\n        onOpenChange?.(value)\n      },\n      [isControlled, onOpenChange]\n    )\n\n    const currentStepData = steps[currentStep]\n\n    // Update target rect when step changes\n    React.useEffect(() => {\n      if (!open || !currentStepData) return\n\n      const updateRect = () => {\n        const element = getTargetElement(currentStepData.target)\n        if (element) {\n          const rect = element.getBoundingClientRect()\n          setTargetRect(rect)\n\n          // Scroll element into view\n          element.scrollIntoView({\n            behavior: 'smooth',\n            block: 'center',\n          })\n        } else if (currentStepData.placement === 'center') {\n          setTargetRect(null)\n        }\n      }\n\n      updateRect()\n\n      // Update on resize/scroll\n      window.addEventListener('resize', updateRect)\n      window.addEventListener('scroll', updateRect, true)\n\n      return () => {\n        window.removeEventListener('resize', updateRect)\n        window.removeEventListener('scroll', updateRect, true)\n      }\n    }, [open, currentStep, currentStepData])\n\n    // Reset step when closed\n    React.useEffect(() => {\n      if (!open) {\n        setCurrentStep(0)\n      }\n    }, [open])\n\n    const nextStep = React.useCallback(() => {\n      if (currentStep < steps.length - 1) {\n        setCurrentStep((prev) => prev + 1)\n      } else {\n        setOpen(false)\n        onComplete?.()\n      }\n    }, [currentStep, steps.length, setOpen, onComplete])\n\n    const prevStep = React.useCallback(() => {\n      if (currentStep > 0) {\n        setCurrentStep((prev) => prev - 1)\n      }\n    }, [currentStep])\n\n    const goToStep = React.useCallback((index: number) => {\n      if (index >= 0 && index < steps.length) {\n        setCurrentStep(index)\n      }\n    }, [steps.length])\n\n    const close = React.useCallback(() => {\n      setOpen(false)\n      if (currentStep === steps.length - 1) {\n        onComplete?.()\n      }\n    }, [setOpen, currentStep, steps.length, onComplete])\n\n    const skip = React.useCallback(() => {\n      setOpen(false)\n      onSkip?.()\n    }, [setOpen, onSkip])\n\n    const contextValue = React.useMemo<TourContextValue>(\n      () => ({\n        currentStep,\n        totalSteps: steps.length,\n        nextStep,\n        prevStep,\n        goToStep,\n        close,\n        skip,\n      }),\n      [currentStep, steps.length, nextStep, prevStep, goToStep, close, skip]\n    )\n\n    if (!open || !currentStepData) return null\n\n    return createPortal(\n      <TourContext.Provider value={contextValue}>\n        <div ref={ref}>\n          <TourOverlay\n            targetRect={targetRect}\n            spotlightPadding={currentStepData.spotlightPadding ?? 8}\n          />\n          <TourPopover\n            step={currentStepData}\n            targetRect={targetRect}\n            showSkipButton={showSkipButton}\n            showProgress={showProgress}\n          />\n        </div>\n      </TourContext.Provider>,\n      document.body\n    )\n  }\n)\nTour.displayName = 'Tour'\n\nexport { Tour, useTour }\n",
      "type": "registry:ui",
      "target": "components/ui/tour.tsx"
    }
  ]
}