{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tour",
  "type": "registry:ui",
  "title": "Tour",
  "description": "تور معرفی",
  "dependencies": [
    "@kashi-ui/persian@^1.8.0"
  ],
  "registryDependencies": [
    "https://kashi.sepantapouya.com/r/cx.json",
    "https://kashi.sepantapouya.com/r/kashi-provider.json"
  ],
  "files": [
    {
      "path": "registry/kashi/Tour.tsx",
      "type": "registry:ui",
      "target": "components/kashi/Tour.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useId, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { toFaDigits } from \"@kashi-ui/persian\";\nimport { useKashi } from \"./KashiProvider\";\n\nexport interface TourStep {\n  /** CSS selector عنصری که این مرحله دربارهٔ آن است، مثل \"#export-btn\" */\n  target: string;\n  title: ReactNode;\n  content: ReactNode;\n}\n\nexport interface TourProps {\n  steps: TourStep[];\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  /** پس از آخرین مرحله */\n  onFinish?: () => void;\n  labels?: { next?: string; prev?: string; done?: string; skip?: string; step?: (n: number, total: number) => string };\n}\n\n/**\n * راهنمای قدم‌به‌قدم برای آشنایی با محصول: بخش هر مرحله روشن و بقیه کم‌رنگ می‌شود.\n * Esc می‌بندد، ←→ بین مرحله‌ها و فوکوس پس از پایان به جای قبلی برمی‌گردد.\n */\nexport function Tour({ steps, open, onOpenChange, onFinish, labels = {} }: TourProps) {\n  const id = useId();\n  const { messages: M, n: N } = useKashi();\n  const { next = M.common.next, prev = M.common.prev, done = M.tour.done, skip = M.tour.skip, step: stepLabel = (a: number, t: number) => M.tour.step(N(a), N(t)) } = labels;\n  const [i, setI] = useState(0);\n  const [rect, setRect] = useState<DOMRect | null>(null);\n  const card = useRef<HTMLDivElement>(null);\n  const [cardPos, setCardPos] = useState({ top: 0, left: 0 });\n  const returnTo = useRef<HTMLElement | null>(null);\n  const s = steps[i];\n\n  useEffect(() => {\n    if (open) {\n      returnTo.current = document.activeElement as HTMLElement;\n      setI(0);\n    } else returnTo.current?.focus?.();\n  }, [open]);\n\n  // جای عنصر هدف را دنبال می‌کند\n  useLayoutEffect(() => {\n    if (!open || !s) return;\n    const el = document.querySelector<HTMLElement>(s.target);\n    if (!el) return setRect(null);\n    el.scrollIntoView({ block: \"center\", behavior: matchMedia(\"(prefers-reduced-motion: reduce)\").matches ? \"auto\" : \"smooth\" });\n    const update = () => setRect(el.getBoundingClientRect());\n    update();\n    const t = setTimeout(update, 400);\n    addEventListener(\"scroll\", update, { passive: true });\n    addEventListener(\"resize\", update);\n    return () => {\n      clearTimeout(t);\n      removeEventListener(\"scroll\", update);\n      removeEventListener(\"resize\", update);\n    };\n  }, [open, i, s?.target]);\n\n  // کارت زیر عنصر، یا بالای آن اگر جا نباشد\n  useLayoutEffect(() => {\n    if (!open || !card.current) return;\n    const c = card.current.getBoundingClientRect();\n    if (!rect) return setCardPos({ top: innerHeight / 2 - c.height / 2, left: innerWidth / 2 - c.width / 2 });\n    const below = rect.bottom + 12 + c.height < innerHeight;\n    const top = below ? rect.bottom + 12 : Math.max(12, rect.top - 12 - c.height);\n    const left = Math.min(innerWidth - c.width - 12, Math.max(12, rect.left + rect.width / 2 - c.width / 2));\n    setCardPos({ top, left });\n    card.current.focus({ preventScroll: true });\n  }, [rect, open, i]);\n\n  if (!open || !s) return null;\n  const last = i === steps.length - 1;\n  const finish = () => {\n    onOpenChange(false);\n    onFinish?.();\n  };\n  return (\n    <>\n      {rect && <div className=\"k-tour__spot\" aria-hidden=\"true\" style={{ top: rect.top - 6, left: rect.left - 6, inlineSize: rect.width + 12, blockSize: rect.height + 12 }} />}\n      <div\n        ref={card}\n        className=\"k-tour__card\"\n        role=\"dialog\"\n        aria-modal=\"false\"\n        aria-labelledby={`${id}-t`}\n        aria-describedby={`${id}-b`}\n        tabIndex={-1}\n        style={{ top: cardPos.top, left: cardPos.left }}\n        onKeyDown={(e) => {\n          if (e.key === \"Escape\") onOpenChange(false);\n          const rtl = getComputedStyle(e.currentTarget).direction === \"rtl\";\n          if (e.key === (rtl ? \"ArrowLeft\" : \"ArrowRight\") && !last) setI(i + 1);\n          if (e.key === (rtl ? \"ArrowRight\" : \"ArrowLeft\") && i > 0) setI(i - 1);\n        }}\n      >\n        <span className=\"k-tour__step\">{stepLabel(i + 1, steps.length)}</span>\n        <p className=\"k-tour__title\" id={`${id}-t`}>{s.title}</p>\n        <p className=\"k-tour__body\" id={`${id}-b`}>{s.content}</p>\n        <div className=\"k-tour__actions\">\n          <button type=\"button\" className=\"k-btn k-btn--link k-tour__skip\" onClick={() => onOpenChange(false)}>{skip}</button>\n          <span className=\"k-tour__progress\" aria-hidden=\"true\">{steps.map((_, k) => <span key={k} data-on={k <= i ? \"\" : undefined} />)}</span>\n          {i > 0 && <button type=\"button\" className=\"k-btn k-btn--secondary k-btn--sm\" onClick={() => setI(i - 1)}>{prev}</button>}\n          <button type=\"button\" className=\"k-btn k-btn--primary k-btn--sm\" onClick={() => (last ? finish() : setI(i + 1))}>{last ? done : next}</button>\n        </div>\n      </div>\n    </>\n  );\n}\n"
    }
  ]
}