{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tour",
  "type": "registry:ui",
  "title": "Tour",
  "author": "Hin",
  "description": "A tour component.",
  "registryDependencies": [
    "button",
    "card",
    "popover"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/tour.tsx",
      "content": "\"use client\"\n\nimport type { Content } from \"@radix-ui/react-popover\"\nimport { XIcon } from \"lucide-react\"\nimport Link from \"next/link\"\nimport * as React from \"react\"\nimport { createPortal } from \"react-dom\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/registry/new-york-v4/ui/button\"\nimport {\n    Card,\n    CardAction,\n    CardContent,\n    CardDescription,\n    CardFooter,\n    CardHeader,\n    CardTitle,\n} from \"@/registry/new-york-v4/ui/card\"\nimport {\n    Popover,\n    PopoverAnchor,\n    PopoverContent,\n} from \"@/registry/new-york-v4/ui/popover\"\n\nconst TourContext = React.createContext<{\n    start: (tourId: string) => void\n    close: () => void\n} | null>(null)\n\nfunction useTour() {\n    const context = React.useContext(TourContext)\n    if (!context) {\n        throw new Error(\"useTour must be used within a TourProvider\")\n    }\n    return context\n}\n\ninterface Step {\n    id: string\n    title: React.ReactNode\n    content: React.ReactNode\n    nextRoute?: string\n    previousRoute?: string\n    nextLabel?: React.ReactNode\n    previousLabel?: React.ReactNode\n    side?: React.ComponentProps<typeof Content>[\"side\"]\n    sideOffset?: React.ComponentProps<typeof Content>[\"sideOffset\"]\n    align?: React.ComponentProps<typeof Content>[\"align\"]\n    alignOffset?: React.ComponentProps<typeof Content>[\"alignOffset\"]\n    className?: string\n}\n\ninterface Tour {\n    id: string\n    steps: Step[]\n}\n\nfunction TourProvider({\n    tours,\n    children,\n}: {\n    tours: Tour[]\n    children: React.ReactNode\n}) {\n    const [isOpen, setIsOpen] = React.useState(false)\n    const [activeTourId, setActiveTourId] = React.useState<string | null>(null)\n    const [currentStepIndex, setCurrentStepIndex] = React.useState(0)\n\n    const activeTour = tours.find((tour) => tour.id === activeTourId)\n    const steps = activeTour?.steps || []\n\n    function next() {\n        if (currentStepIndex < steps.length - 1) {\n            setCurrentStepIndex((prev) => prev + 1)\n        } else {\n            setIsOpen(false)\n            setCurrentStepIndex(0)\n            setActiveTourId(null)\n        }\n    }\n\n    function previous() {\n        if (currentStepIndex > 0) {\n            setCurrentStepIndex((prev) => prev - 1)\n        }\n    }\n\n    function close() {\n        setIsOpen(false)\n        setCurrentStepIndex(0)\n        setActiveTourId(null)\n    }\n\n    function start(tourId: string) {\n        const tour = tours.find((tour) => tour.id === tourId)\n        if (tour) {\n            if (tour.steps.length > 0) {\n                setActiveTourId(tourId)\n                setIsOpen(true)\n                setCurrentStepIndex(0)\n            } else {\n                console.error(`Tour with id '${tourId}' has no steps.`)\n            }\n        } else {\n            console.error(`Tour with id '${tourId}' not found.`)\n        }\n    }\n\n    return (\n        <TourContext.Provider\n            value={{\n                start,\n                close,\n            }}>\n            {children}\n            {isOpen && activeTour && steps.length > 0 && (\n                <TourOverlay\n                    step={steps[currentStepIndex]}\n                    currentStepIndex={currentStepIndex}\n                    totalSteps={steps.length}\n                    onNext={next}\n                    onPrevious={previous}\n                    onClose={close}\n                />\n            )}\n        </TourContext.Provider>\n    )\n}\n\nfunction TourOverlay({\n    step,\n    currentStepIndex,\n    totalSteps,\n    onNext,\n    onPrevious,\n    onClose,\n}: {\n    step: Step\n    currentStepIndex: number\n    totalSteps: number\n    onNext: () => void\n    onPrevious: () => void\n    onClose: () => void\n}) {\n    const [targets, setTargets] = React.useState<\n        { rect: DOMRect; radius: number }[]\n    >([])\n\n    React.useEffect(() => {\n        let needsScroll = true\n\n        function updatePosition() {\n            const elements = document.querySelectorAll(\n                `[data-tour-step-id*='${step.id}']`\n            )\n\n            if (elements.length > 0) {\n                const validElements: {\n                    rect: {\n                        width: number\n                        height: number\n                        x: number\n                        y: number\n                        left: number\n                        top: number\n                        right: number\n                        bottom: number\n                        toJSON: () => void\n                    }\n                    radius: number\n                    element: Element\n                }[] = []\n\n                Array.from(elements).forEach((element) => {\n                    const rect = element.getBoundingClientRect()\n                    if (rect.width === 0 && rect.height === 0) return\n\n                    const style = window.getComputedStyle(element)\n                    const radius = Number(style.borderRadius) || 4\n\n                    validElements.push({\n                        rect: {\n                            width: rect.width,\n                            height: rect.height,\n                            x: rect.left,\n                            y: rect.top,\n                            left: rect.left,\n                            top: rect.top,\n                            right: rect.right,\n                            bottom: rect.bottom,\n                            toJSON: () => {},\n                        },\n                        radius,\n                        element,\n                    })\n                })\n\n                setTargets(\n                    validElements.map(({ rect, radius }) => ({ rect, radius }))\n                )\n\n                if (validElements.length > 0 && needsScroll) {\n                    validElements[0].element.scrollIntoView({\n                        behavior: \"smooth\",\n                        block: \"center\",\n                    })\n                    needsScroll = false\n                }\n            } else {\n                setTargets([])\n            }\n        }\n\n        updatePosition()\n        const handleResizeOrScroll = () => updatePosition()\n\n        window.addEventListener(\"resize\", handleResizeOrScroll)\n        window.addEventListener(\"scroll\", handleResizeOrScroll, true)\n\n        const observer = new MutationObserver(() => updatePosition())\n        observer.observe(document.body, {\n            attributes: true,\n            childList: true,\n            subtree: true,\n        })\n\n        const resizeObserver = new ResizeObserver(() => updatePosition())\n        resizeObserver.observe(document.body)\n\n        return () => {\n            window.removeEventListener(\"resize\", handleResizeOrScroll)\n            window.removeEventListener(\"scroll\", handleResizeOrScroll, true)\n            observer.disconnect()\n            resizeObserver.disconnect()\n        }\n    }, [step])\n\n    React.useEffect(() => {\n        document.body.style.overflow = \"hidden\"\n        return () => {\n            document.body.style.overflow = \"\"\n        }\n    }, [])\n\n    if (!document) return null\n    if (targets.length === 0) return null\n\n    return createPortal(\n        <div className=\"fixed inset-0 z-50\">\n            <svg className=\"absolute inset-0 size-full\">\n                <defs>\n                    <mask id=\"tour-mask\">\n                        <rect\n                            x=\"0\"\n                            y=\"0\"\n                            width=\"100%\"\n                            height=\"100%\"\n                            fill=\"white\"\n                        />\n                        {targets.map((target, i) => (\n                            <rect\n                                key={i}\n                                x={target.rect.left}\n                                y={target.rect.top}\n                                width={target.rect.width}\n                                height={target.rect.height}\n                                rx={target.radius}\n                                fill=\"black\"\n                            />\n                        ))}\n                    </mask>\n                </defs>\n                <rect\n                    width=\"100%\"\n                    height=\"100%\"\n                    mask=\"url(#tour-mask)\"\n                    className=\"fill-black opacity-20\"\n                />\n                {targets.map((target, i) => {\n                    return (\n                        <rect\n                            key={i}\n                            x={target.rect.left}\n                            y={target.rect.top}\n                            width={target.rect.width}\n                            height={target.rect.height}\n                            rx={target.radius}\n                            className=\"stroke-primary fill-none stroke-2\"\n                        />\n                    )\n                })}\n            </svg>\n            {targets.length > 0 && (\n                <Popover key={step.id} open={true}>\n                    <PopoverAnchor\n                        virtualRef={{\n                            current: {\n                                getBoundingClientRect: () =>\n                                    targets[0]?.rect || {\n                                        top: 0,\n                                        left: 0,\n                                        width: 0,\n                                        height: 0,\n                                        bottom: 0,\n                                        right: 0,\n                                        x: 0,\n                                        y: 0,\n                                        toJSON: () => {},\n                                    },\n                            },\n                        }}\n                    />\n                    <PopoverContent\n                        className={cn(\"px-0\", step.className)}\n                        side={step.side}\n                        sideOffset={step.sideOffset}\n                        align={step.align}\n                        alignOffset={step.alignOffset}\n                        onOpenAutoFocus={(e) => e.preventDefault()}\n                        onCloseAutoFocus={(e) => e.preventDefault()}\n                        asChild>\n                        <Card>\n                            <CardHeader>\n                                <CardTitle>{step.title}</CardTitle>\n                                <CardDescription>\n                                    Step {currentStepIndex + 1} of {totalSteps}\n                                </CardDescription>\n                                <CardAction>\n                                    <Button\n                                        variant=\"ghost\"\n                                        size=\"icon\"\n                                        onClick={onClose}>\n                                        <XIcon />\n                                    </Button>\n                                </CardAction>\n                            </CardHeader>\n                            <CardContent>{step.content}</CardContent>\n                            <CardFooter className=\"justify-between\">\n                                {currentStepIndex > 0 &&\n                                    (step.previousRoute ? (\n                                        <Button\n                                            variant=\"outline\"\n                                            onClick={onPrevious}\n                                            asChild>\n                                            <Link href={step.previousRoute}>\n                                                {step.previousLabel ??\n                                                    \"Previous\"}\n                                            </Link>\n                                        </Button>\n                                    ) : (\n                                        <Button\n                                            variant=\"outline\"\n                                            onClick={onPrevious}>\n                                            {step.previousLabel ?? \"Previous\"}\n                                        </Button>\n                                    ))}\n                                {step.nextRoute ? (\n                                    <Button\n                                        className=\"ml-auto\"\n                                        onClick={onNext}\n                                        asChild>\n                                        <Link href={step.nextRoute}>\n                                            {step.nextLabel ??\n                                                (currentStepIndex ===\n                                                totalSteps - 1\n                                                    ? \"Finish\"\n                                                    : \"Next\")}\n                                        </Link>\n                                    </Button>\n                                ) : (\n                                    <Button\n                                        className=\"ml-auto\"\n                                        onClick={onNext}>\n                                        {step.nextLabel ??\n                                            (currentStepIndex === totalSteps - 1\n                                                ? \"Finish\"\n                                                : \"Next\")}\n                                    </Button>\n                                )}\n                            </CardFooter>\n                        </Card>\n                    </PopoverContent>\n                </Popover>\n            )}\n        </div>,\n        document.body\n    )\n}\n\nexport { TourProvider, useTour, type Step, type Tour }\n",
      "type": "registry:ui"
    }
  ]
}