import React, { useState, useEffect } from 'react'; import { X, ChevronRight, Check } from 'lucide-react'; interface TourStep { target: string; title: string; description: string; placement?: 'top' | 'bottom' | 'left' | 'right'; } interface OnboardingTourProps { steps: TourStep[]; onComplete: () => void; onSkip: () => void; } const OnboardingTour: React.FC = ({ steps, onComplete, onSkip }) => { const [currentStep, setCurrentStep] = useState(0); const [position, setPosition] = useState({ top: 0, left: 0 }); useEffect(() => { // Remove previous highlights document.querySelectorAll('.tour-highlight').forEach(el => { el.classList.remove('tour-highlight'); }); const updatePosition = () => { const target = document.querySelector(steps[currentStep].target); if (!target) { console.warn(`Tour target not found: ${steps[currentStep].target}`); // Try again after a short delay setTimeout(updatePosition, 100); return; } // 팝업을 화면 기준으로 고정 배치 (스크롤에 영향받지 않음) const viewportWidth = window.innerWidth; // 모바일/데스크톱 구분 const isMobile = viewportWidth < 768; let top: number; let left: number; if (isMobile) { // 모바일: 화면 하단 고정 top = 20; // viewport 기준 left = 20; } else { // 데스크톱: 화면 우측 상단 고정 top = 20; // viewport 기준 (상단에서 20px) left = viewportWidth - 340; // 우측에서 340px (카드 너비 320 + 여유 20) } setPosition({ top, left }); // Highlight target element target.classList.add('tour-highlight'); // Scroll into view target.scrollIntoView({ behavior: 'smooth', block: 'center' }); }; // Wait for DOM to be ready const timer = setTimeout(updatePosition, 150); window.addEventListener('resize', updatePosition); window.addEventListener('scroll', updatePosition); return () => { clearTimeout(timer); window.removeEventListener('resize', updatePosition); window.removeEventListener('scroll', updatePosition); }; }, [currentStep, steps]); const handleNext = () => { if (currentStep < steps.length - 1) { setCurrentStep(currentStep + 1); } else { onComplete(); } }; const handlePrev = () => { if (currentStep > 0) { setCurrentStep(currentStep - 1); } }; const currentStepData = steps[currentStep]; return ( <> {/* Overlay - 매우 투명하게 */}
{/* Tour Card - viewport 기준 고정 위치 */}
{/* Header */}
{steps.map((_, idx) => (
))}
{currentStep + 1} / {steps.length}
{/* Content */}

{currentStep + 1} {currentStepData.title}

{currentStepData.description}

{/* Footer */}
{currentStep > 0 && ( )}
{/* CSS for highlight effect */} ); }; export default OnboardingTour;