Skip to content

feat(Tour): new component#5781

Open
solidusite wants to merge 7 commits intonuxt:v4from
solidusite:feat/tour-component
Open

feat(Tour): new component#5781
solidusite wants to merge 7 commits intonuxt:v4from
solidusite:feat/tour-component

Conversation

@solidusite
Copy link
Copy Markdown
Contributor

@solidusite solidusite commented Dec 29, 2025

🔗 Linked issue

Resolves #4874

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

The Tour component provides a guided tour experience that highlights specific elements and displays step-by-step instructions. Each tour consists of multiple steps, where each step can target an element by ID selector, CSS class selector, or a ref function. The component automatically highlights target elements with a customizable border and shadow effect, positioning the tour content (popover) relative to each target.

Key features include navigation controls (next, previous, finish) with customizable labels per step, a loop option to automatically restart the tour, and modal mode to block interaction with outside elements for better accessibility. Steps can be configured with custom content positioning, dismissible behavior, and optional arrows. The tour supports custom slots for complete UI customization and includes built-in accessibility features with proper ARIA labels and screen reader support.

Registrazione.schermo.2025-12-29.alle.22.22.09.mov
image

Overlay effect [NEW]

Registrazione.schermo.2026-02-14.alle.11.19.14.mov

TODO

  • Overlay effect
  • i18n translate

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Dec 29, 2025

npm i https://pkg.pr.new/@nuxt/ui@5781

commit: de565f7

@solidusite solidusite marked this pull request as draft December 30, 2025 08:13
@branislavjuhaas
Copy link
Copy Markdown
Contributor

This looks really cool!

@T-Zahil
Copy link
Copy Markdown

T-Zahil commented Jan 8, 2026

Love it!

@solidusite
Copy link
Copy Markdown
Contributor Author

@benjamincanac what about this one? I know this implementation is a challenge, but maybe we can get a very basic but functional initial version. It's a very interesting component to have integrated into Nuxt. I'll probably need some help finalizing it with a code review.

@solidusite
Copy link
Copy Markdown
Contributor Author

Overlay effect [NEW]

Registrazione.schermo.2026-02-14.alle.11.19.14.mov

I think it's stable now @benjamincanac
that's would be great if merged! I need it too in my projects :D

@solidusite solidusite marked this pull request as ready for review February 14, 2026 10:25
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Tour feature: a runtime Tour.vue component with TypeScript types and theme styles, localized strings for many locales, utilities for locale fallback, and examples/docs/playground pages demonstrating usage (basic, custom labels, loop, modal, overlay). Exposes the Tour export in runtime types and theme index, updates theme with tour styles, and adds tests for the Tour component. Several documentation and example Vue SFCs plus a new docs page and a playground route were added. Minor navigation update includes adding a "tour" entry.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(Tour): new component' clearly and concisely describes the main change—adding a new Tour component feature.
Description check ✅ Passed The PR description provides comprehensive details about the Tour component's features, including step configuration, navigation, modal mode, accessibility, overlay effects, and links the issue #4874.
Linked Issues check ✅ Passed The PR successfully implements all core requirements from issue #4874: step-based targeting (ID/CSS/ref), configurable highlight styles with overlay effect, navigation controls, loop support, modal mode, accessibility features (ARIA labels, keyboard navigation), theming via Nuxt UI tokens, and complete documentation.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Tour component feature. Additions include the Tour component itself, example components, documentation, locale translations, theme configuration, tests, and navigation updates—all within scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In `@src/runtime/locale/sv.ts`:
- Around line 130-135: The tour strings in the locale files (e.g., the tour
object in sv.ts with keys next, previous, finish, close) are still in English;
update the values for tour.next, tour.previous, tour.finish and tour.close in
sv.ts (and replicate fixes across the other non-English locale files) to their
correct Swedish translations (or pull verified translations from your i18n
source/translator). Ensure you update the tour object in each locale file (not
just carousel.dots), run i18n validation/tests, and commit the translated
strings so the UI displays localized tour controls.
🟠 Major comments (26)
src/runtime/locale/kk.ts-130-135 (1)

130-135: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated across 58 locales.

The tour labels in this and nearly all non-English locale files remain in English. Only en.ts and en_gb.ts correctly have English; all other 58 locale files need translations for next, previous, finish, and close.

Affected locales include: kk, sk, ru, th, and 54 others (ar, bg, bn, ca, cs, de, es, fr, ja, ko, pt, zh_cn, etc.).

Translate the tour labels before merging, or defer the entire tour feature to a separate localization pass.

src/runtime/locale/bg.ts-130-135 (1)

130-135: ⚠️ Potential issue | 🟠 Major

Tour strings are not translated to Bulgarian.

Same issue as the other locale files — English placeholders instead of Bulgarian translations. Compare with carousel.next: 'Напред'.

Suggested translations (verify with a native speaker)
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Напред',
+      previous: 'Назад',
+      finish: 'Завърши',
+      close: 'Пропусни'
     }
src/runtime/locale/it.ts-130-135 (1)

130-135: ⚠️ Potential issue | 🟠 Major

Tour strings are not translated to Italian.

English placeholders instead of Italian. Compare with carousel.next: 'Successiva'.

Suggested translations (verify with a native speaker)
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Avanti',
+      previous: 'Indietro',
+      finish: 'Fine',
+      close: 'Salta'
     }
src/runtime/locale/tr.ts-130-135 (1)

130-135: ⚠️ Potential issue | 🟠 Major

Tour strings are not translated to Turkish.

All four values are English placeholders. Compare with the rest of this file (e.g., carousel.next: 'Sonraki', carousel.prev: 'Önceki'). These should be translated to Turkish.

Suggested translations (verify with a native speaker)
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Sonraki',
+      previous: 'Önceki',
+      finish: 'Bitir',
+      close: 'Atla'
     }
src/runtime/locale/mn.ts-130-135 (1)

130-135: ⚠️ Potential issue | 🟠 Major

Tour strings are not translated to Mongolian.

English placeholders instead of Mongolian. Compare with carousel.next: 'Дараах'.

Suggested translations (verify with a native speaker)
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Дараах',
+      previous: 'Өмнөх',
+      finish: 'Дуусгах',
+      close: 'Алгасах'
     }
src/runtime/locale/he.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟠 Major

Tour strings are not translated to Hebrew.

English placeholders instead of Hebrew. Compare with carousel.next: 'הבא'.

Suggested translations (verify with a native speaker)
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'הבא',
+      previous: 'הקודם',
+      finish: 'סיום',
+      close: 'דלג'
     }
src/runtime/locale/lo.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour strings are not translated to Lao and across nearly all non-English locales.

The tour object in this file contains English placeholders instead of Lao translations, while all other messages are properly localized in Lao (ລາວ). This is a systemic issue affecting 58 non-English locale files in the repository—only the Belarusian locale has translated tour strings.

Proposed fix for Lao:

Translate tour strings
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'ຕໍ່ໄປ',
+      previous: 'ກ່ອນໜ້າ',
+      finish: 'ສຳເລັດ',
+      close: 'ຂ້າມ'
     }

Use the carousel translations already in this file (ຕໍ່ໄປ for "next" and ກ່ອນໜ້າ for "previous") for consistency.

src/runtime/locale/zh_tw.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Translate tour strings in all affected locale files.

The tour object in zh_tw.ts (lines 130-134) and 6 other locale files (pt, ar, uk, ko, ug_cn, hu, lb) contains only English text, while every other section in these files is properly localized. This breaks i18n for the tour feature and leaves non-English users with untranslated UI elements.

Proposed fix for zh_tw.ts
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: '下一步',
+      previous: '上一步',
+      finish: '完成',
+      close: '跳過'
     }
src/runtime/locale/hy.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Armenian.

All four tour values are English placeholders. Every other section in this file uses Armenian translations. These should be localized to match (e.g., next: 'Հաջորդ', previous: 'Նախորդ', finish: 'Ավdelays', close: 'Բաdelays').

src/runtime/locale/zh_cn.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Simplified Chinese.

All four values are English placeholders. Every other section uses Chinese translations. These should be localized, e.g.:

Suggested translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: '下一步',
+      previous: '上一步',
+      finish: '完成',
+      close: '跳过'
     }
src/runtime/locale/pt_br.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Brazilian Portuguese.

All four values are English. Based on existing patterns (e.g., carousel.next: 'Próximo', carousel.prev: 'Anterior', modal.close: 'Fechar'), these should be localized, e.g.:

Suggested translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Próximo',
+      previous: 'Anterior',
+      finish: 'Concluir',
+      close: 'Pular'
     }
src/runtime/locale/sq.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Albanian.

All four values are English placeholders. Every other section in this file uses Albanian translations. These should be localized, e.g.:

Suggested translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Tjetri',
+      previous: 'Para',
+      finish: 'Përfundo',
+      close: 'Kalo'
     }
src/runtime/locale/eu.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Basque.

All four values are English. Based on existing patterns (e.g., carousel.next: 'Hurrengoa', carousel.prev: 'Aurretikoa', modal.close: 'Itxi'), these should be localized, e.g.:

Suggested translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Hurrengoa',
+      previous: 'Aurretikoa',
+      finish: 'Amaitu',
+      close: 'Saltatu'
     }
src/runtime/locale/fi.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Finnish.

All four values are English. Based on existing patterns (e.g., carousel.next: 'Seuraava', carousel.prev: 'Edellinen', modal.close: 'Sulje'), these should be localized, e.g.:

Suggested translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Seuraava',
+      previous: 'Edellinen',
+      finish: 'Valmis',
+      close: 'Ohita'
     }
src/runtime/locale/ckb.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Central Kurdish.

All four values are English. Every other section uses Kurdish script. Based on existing patterns (e.g., carousel.next: 'دواتر', carousel.prev: 'پێشتر', modal.close: 'داخستن'), these should be localized, e.g.:

Suggested translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'دواتر',
+      previous: 'پێشتر',
+      finish: 'تەواوکردن',
+      close: 'بازدان'
     }
src/runtime/locale/nl.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Dutch.

All four values are English. Based on existing patterns in this file (e.g., carousel.next: 'Volgende', carousel.prev: 'Vorige', modal.close: 'Sluiten'), these should be something like:

Suggested translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Volgende',
+      previous: 'Vorige',
+      finish: 'Voltooien',
+      close: 'Overslaan'
     }
src/runtime/locale/de_ch.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Swiss German.

These should use Swiss German strings (e.g., next: 'Weiter', previous: 'Zurück', finish: 'Fertig', close: 'Überspringen'), consistent with existing patterns like carousel.next: 'Weiter'.

src/runtime/locale/ms.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Malay.

These should use Malay strings (e.g., next: 'Seterusnya', previous: 'Sebelum', finish: 'Selesai', close: 'Langkau'), consistent with existing patterns like carousel.next: 'Seterusnya'.

src/runtime/locale/lt.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Lithuanian.

These should use Lithuanian strings (e.g., next: 'Pirmyn', previous: 'Atgal', finish: 'Baigti', close: 'Praleisti'), consistent with existing patterns like carousel.next: 'Pirmyn'.

src/runtime/locale/ka.ts-132-136 (1)

132-136: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Georgian.

All other sections in this locale file use proper Georgian translations, but the tour keys are left as English placeholders. These should be translated to Georgian (e.g., next: 'შემდეგი', previous: 'წინა', finish: 'დასრულება', close: 'გამოტოვება'), consistent with how similar terms are translated elsewhere in this file (e.g., carousel.next is 'შემდეგი').

src/runtime/locale/ky.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Kyrgyz.

These should use Kyrgyz strings (e.g., next: 'Кийинки', previous: 'Алдыңкы', finish: 'Аяктоо', close: 'Өткөрүп жиберүү'), consistent with existing patterns like carousel.next: 'Кийинки'.

src/runtime/locale/ro.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Romanian.

Same issue as the other locale files — these should use Romanian strings (e.g., next: 'Următor', previous: 'Anterior', finish: 'Finalizare', close: 'Omite'), consistent with existing patterns like carousel.next: 'Următor'.

src/runtime/locale/uz.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour labels are not translated into Uzbek.

These should use Uzbek strings (e.g., next: 'Oldinga', previous: 'Ortga', finish: 'Tugatish', close: 'O'tkazib yuborish'), consistent with existing patterns like carousel.next: 'Oldinga'.

This issue affects all non-English locale files in the repository—59 files have untranslated English tour strings despite having translations for other UI components. Consider addressing them all in a single pass.

src/runtime/components/Tour.vue-296-304 (1)

296-304: ⚠️ Potential issue | 🟠 Major

Having a custom nextLabel on the last step silently enables looping.

Line 299: if (props.loop || currentStep.value?.nextLabel) restarts the tour when the last step has a nextLabel. A user might set nextLabel: "Let's Go!" purely for label customization without intending the tour to loop. This couples label text to navigation behavior, which is surprising.

Proposed fix — decouple label from loop behavior
-  // If loop is enabled OR if there's a custom nextLabel on the last step, restart the tour
-  if (props.loop || currentStep.value?.nextLabel) {
+  if (props.loop) {
     currentIndex.value = 0
     emits('next', currentIndex.value)
     nextTick(updateHighlight)
     return
   }

The button label logic in the template (lines 435-437) already handles displaying the correct label when nextLabel is set, so removing it from the loop condition won't break the label display.

src/runtime/components/Tour.vue-199-214 (1)

199-214: ⚠️ Potential issue | 🟠 Major

dismissible prop is effectively ignored when modal is true (the default).

With defaults modal: true and dismissible: true, the computed shouldPreventDismissal evaluates to !true || true = true, so Escape and click-outside are always blocked. A user who sets dismissible: true (or relies on the default) would expect the tour to be closeable via Escape, but it won't be. The dismissible prop becomes a no-op unless modal is also set to false.

If the intent is that dismissible controls close-on-escape/click-outside independently of modal, the condition should use AND:

Proposed fix
-// When modal is true, we should also prevent dismissal to ensure proper behavior
-const shouldPreventDismissal = computed(() => !dismissible.value || props.modal)
+// Prevent dismissal only when explicitly disabled
+const shouldPreventDismissal = computed(() => !dismissible.value)

If the intent is that modal mode should always block outside interaction (but still allow Escape), consider splitting the event prevention so Escape is governed by dismissible alone:

Alternative: split escape vs. outside-click
 const contentEvents = computed(() => {
-  if (shouldPreventDismissal.value) {
-    const events = ['pointerDownOutside', 'interactOutside', 'escapeKeyDown']
-
-    return events.reduce((acc, curr) => {
-      acc[curr] = (e: Event) => {
-        e.preventDefault()
-      }
-      return acc
-    }, {} as Record<typeof events[number], (e: Event) => void>)
+  const events: Record<string, (e: Event) => void> = {}
+  if (props.modal || !dismissible.value) {
+    events.pointerDownOutside = (e: Event) => e.preventDefault()
+    events.interactOutside = (e: Event) => e.preventDefault()
+  }
+  if (!dismissible.value) {
+    events.escapeKeyDown = (e: Event) => e.preventDefault()
   }
-
-  return {}
+  return events
 })
src/runtime/locale/gl.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟠 Major

Tour strings are untranslated across 60 locale files.

The issue in src/runtime/locale/gl.ts (lines 130–134) is part of a systemic problem: nearly every non-English locale file contains English tour strings instead of localized translations.

In gl.ts specifically, other keys are properly translated to Galician (e.g., carousel.next → "Seguinte", carousel.prev → "Anterior"), confirming that tour strings were overlooked.

Proposed Galician translations for gl.ts
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Seguinte',
+      previous: 'Anterior',
+      finish: 'Finalizar',
+      close: 'Saltar'
     }

All 60 affected locale files (ar, az, bg, bn, ca, ckb, cs, da, de, de_ch, el, en_gb, es, et, eu, fa_ir, fi, fr, gl, he, hi, hr, hu, hy, id, it, ja, ka, kk, km, ko, ky, lb, lo, lt, mn, ms, nb_no, nl, pl, pt, pt_br, ro, ru, sk, sl, sq, sv, th, tj, tr, ug_cn, uk, ur, uz, vi, zh_cn, zh_tw) require translation of the tour keys.

🟡 Minor comments (15)
src/runtime/locale/vi.ts-130-135 (1)

130-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated — English placeholders in all non-English locales.

The tour values here are English ("Next", "Previous", "Finish", "Skip") while every other string in this file is properly translated to Vietnamese. The same issue affects all non-English locale files in this PR: da.ts, es.ts, ca.ts, et.ts, az.ts, km.ts, and likely others not included for review.

For Vietnamese, these should be something like:

Suggested translations (verify with a native speaker)
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Tiếp theo',
+      previous: 'Trước',
+      finish: 'Hoàn thành',
+      close: 'Bỏ qua'
     }

Please translate the tour strings for every locale file, not just this one.

src/runtime/locale/id.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated into Indonesian.

Suggested: next: 'Berikutnya', previous: 'Sebelumnya', finish: 'Selesai', close: 'Lewati' — consistent with existing carousel labels in this file.

src/runtime/locale/sl.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated into Slovenian.

Suggested: next: 'Naprej', previous: 'Nazaj', finish: 'Končaj', close: 'Preskoči' — aligning with existing carousel labels.

src/runtime/locale/tj.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated into Tajik.

Suggested: next: 'Баъдӣ', previous: 'Қаблӣ', finish: 'Анҷом', close: 'Гузаштан' — aligning with existing carousel labels.

src/runtime/locale/cs.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated into Czech.

Suggested: next: 'Další', previous: 'Předchozí', finish: 'Dokončit', close: 'Přeskočit' — consistent with existing carousel labels.

src/runtime/locale/ur.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated into Urdu.

All four tour labels are English placeholders while every other key in this locale uses Urdu script. These should be translated (e.g., next: 'اگلا', previous: 'پچھلا', finish: 'ختم', close: 'چھوڑیں').

src/runtime/locale/nb_no.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated into Norwegian Bokmål.

This locale already has equivalent terms (e.g., carousel.next: 'Neste', carousel.prev: 'Forrige'). Consider reusing the same vocabulary: next: 'Neste', previous: 'Forrige', finish: 'Fullfør', close: 'Hopp over'.

src/runtime/locale/hi.ts-131-135 (1)

131-135: ⚠️ Potential issue | 🟡 Minor

Tour strings are not translated into Hindi.

Same issue as other locale files — all four labels are English placeholders. These should be in Hindi (e.g., next: 'अगला', previous: 'पिछला', finish: 'समाप्त', close: 'छोड़ें').

playgrounds/nuxt/app/pages/components/tour.vue-40-42 (1)

40-42: ⚠️ Potential issue | 🟡 Minor

Inconsistent language: "Avvia tour" is Italian; the rest of the page is English.

Suggested fix
-      Avvia tour
+      Start tour
src/runtime/locale/hr.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟡 Minor

Tour labels are not translated to Croatian.

Same issue as other locale files — English placeholders instead of Croatian translations.

🌐 Suggested Croatian translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Sljedeći',
+      previous: 'Prethodni',
+      finish: 'Završi',
+      close: 'Preskoči'
     }
src/runtime/locale/el.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟡 Minor

Tour labels are not translated to Greek.

Same pattern — English placeholders instead of Greek translations.

🌐 Suggested Greek translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Επόμενο',
+      previous: 'Προηγούμενο',
+      finish: 'Τέλος',
+      close: 'Παράλειψη'
     }
src/runtime/locale/fa_ir.ts-130-135 (1)

130-135: ⚠️ Potential issue | 🟡 Minor

Tour labels are not translated to Persian.

All other strings in this locale are properly translated to فارسی, but the tour block contains English values. This should use Persian translations, e.g.:

🌐 Suggested Persian translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'بعدی',
+      previous: 'قبلی',
+      finish: 'پایان',
+      close: 'رد شدن'
     }
src/runtime/locale/fr.ts-130-134 (1)

130-134: ⚠️ Potential issue | 🟡 Minor

Tour labels are not translated to French.

The tour block contains English strings that should be translated. This issue affects 59 out of 61 locale files in the repository. The French translations should be:

🌐 Suggested French translations
     tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
+      next: 'Suivant',
+      previous: 'Précédent',
+      finish: 'Terminer',
+      close: 'Passer'
     }

Please translate the tour strings in all affected locale files.

src/runtime/components/Tour.vue-270-272 (1)

270-272: ⚠️ Potential issue | 🟡 Minor

Misleading comment: Tailwind's outline-offset-2 is 2px, not 8px.

The comment states "outline-offset-2 in Tailwind equals 8px (0.5rem)" — Tailwind's outline-offset-2 utility generates outline-offset: 2px. The class for 8px would be outline-offset-8. If 8px is the correct value for the visual effect, update the comment to reference the right class.

src/runtime/utils/locale.ts-21-32 (1)

21-32: ⚠️ Potential issue | 🟡 Minor

Type annotation on prop is incorrect — get() returns undefined when not found.

Line 22 declares let prop: string but assigns the result of get(locale, \messages.${path}`, undefined), which returns undefinedwhen the path is not found. Line 25 confirms this withprop === undefinedcheck. The type should bestring | undefined`.

Additionally, the prop === path check on line 25 is unnecessary. Since the default in line 22 is undefined (not path), prop === path would only match if the translation value itself equals the path key, which is extremely unlikely. This appears to be dead code or overly defensive logic that should be removed.

Suggested fix
-  let prop: string = get(locale, `messages.${path}`, undefined)
+  let prop: string | undefined = get(locale, `messages.${path}`, undefined)

   // If translation not found and it's not the fallback path itself, try fallback locale
-  if (prop === undefined || prop === path) {
+  if (prop === undefined) {
🧹 Nitpick comments (7)
src/runtime/locale/en_gb.ts (1)

8-16: The explicit tour block is redundant.

en.ts already includes an identical tour object. Since ...en.messages spreads all of en.ts's messages (including tour), the explicit override adds unnecessary maintenance burden—if en.ts tour labels change, this file will silently shadow them.

Simplify to:

-  messages: {
-    ...en.messages,
-    tour: {
-      next: 'Next',
-      previous: 'Previous',
-      finish: 'Finish',
-      close: 'Skip'
-    }
-  }
+  messages: en.messages
test/components/Tour.spec.ts (1)

8-52: Good test coverage for rendering and props/slots.

The parameterized snapshot tests provide solid baseline coverage. Consider adding interaction tests (clicking next/previous, verifying step transitions, testing the close/dismiss behavior) in a follow-up to ensure navigation logic works end-to-end.

src/runtime/utils/locale.ts (1)

40-52: Fallback locale computed once — stale if locale ref changes from 'en' to another code.

Line 45 evaluates unref(locale).code at context creation time to decide whether English fallback is needed. If locale is a Ref that later changes (e.g., 'en''fr'), this fallback constant won't update.

In practice this is mitigated because translate() (line 26) re-derives the fallback internally, so translations still resolve correctly. However, having fallback logic in two places (here and in translate) is redundant and could diverge. Consider computing the fallback solely inside translate and removing it from buildLocaleContext:

Simplification suggestion
-export function buildLocaleContext<M>(locale: MaybeRef<Locale<M>>, fallbackLocale?: Locale<M>): LocaleContext<M> {
+export function buildLocaleContext<M>(locale: MaybeRef<Locale<M>>): LocaleContext<M> {
   const lang = computed(() => unref(locale).name)
   const code = computed(() => unref(locale).code)
   const dir = computed(() => unref(locale).dir)
   const localeRef = isRef(locale) ? locale : ref(locale) as Ref<Locale<M>>
-  const fallback = fallbackLocale || (unref(locale).code !== 'en' ? en as Locale<M> : undefined)

   return {
     lang,
     code,
     dir,
     locale: localeRef,
-    t: buildTranslator(locale, fallback)
+    t: buildTranslator(locale)
   }
 }

This way translate remains the single source of fallback logic and reactivity is preserved.

src/runtime/components/Tour.vue (4)

232-256: resolveTarget has a redundant function check and a potentially confusing double-lookup for plain strings.

Two issues:

  1. Dead code on line 251: toValue(target) on line 233 already invokes functions, so the typeof value === 'function' branch can never be reached for the declared TourTarget type.

  2. String resolution (lines 239-248): Passing a bare string like "my-element" first queries document.querySelector("my-element"), which matches a <my-element> custom-element tag—likely not the user's intent. If no match, it retries with #my-element. Consider reversing the priority or documenting this behavior.

Suggested simplification
 function resolveTarget(target?: TourTarget) {
   const value = toValue(target) as TourTarget

   if (!value || typeof window === 'undefined') {
     return undefined
   }

   if (typeof value === 'string') {
-    const direct = document.querySelector(value) as ReferenceElement | null
-
-    if (direct) {
-      return direct
-    }
-
-    const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`
-
-    return document.querySelector(selector) as ReferenceElement | null
+    // If the string is already a selector (starts with # or .), use it directly.
+    // Otherwise, treat it as an ID.
+    const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`
+    return document.querySelector(selector) as ReferenceElement | null
   }

-  if (typeof value === 'function') {
-    return value() ?? undefined
-  }
-
   return value ?? undefined
 }

418-428: Redundant :disabled — button is only rendered when hasPrev is true.

Line 419 uses v-if="hasPrev" so the button is never rendered when hasPrev is false, making :disabled="!hasPrev" on line 423 always evaluate to false.

Cleanup
                 <UButton
                   v-if="hasPrev"
                   size="xs"
                   color="neutral"
                   variant="ghost"
-                  :disabled="!hasPrev"
                   :class="ui.prev({ class: currentStep?.ui?.prev })"
                   `@click`="prev"
                 >

348-368: Hardcoded z-index values near MAX_INT may collide with other libraries.

Lines 352, 364, 375 use 2147483645, 2147483646, and 2147483647. While layering overlay < highlight < content is correct, consider making these configurable (e.g., via a zIndex prop or theme token) so users can adjust them when integrating with third-party components that also use extreme z-index values.


330-332: Highlight position won't update when a scrollable ancestor (not window) scrolls.

useEventListener on lines 330-331 only listens to the window scroll event. If the target element is inside a scrollable container (e.g., an overflow div), the highlight will drift out of sync. This is a known limitation in many tour libraries, but worth noting. A future improvement could find the nearest scrollable ancestor of the target and listen on that as well.

Comment thread src/runtime/locale/sv.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@src/runtime/locale/et.ts`:
- Around line 130-134: The tour.close translation "Jäta vahele" is inconsistent
with other close keys (e.g., alert.close, modal.close, toast.close) which use
"Sulge"; either change tour.close to "Sulge" to match the intended
"close/dismiss" semantics or, if it truly means "skip", rename the key to
tour.skip (and update all type definitions/usages) so the meaning is explicit;
update the translation and any references to tour.close in code or i18n types
accordingly (look for the tour object and keys named close/skip).

In `@src/runtime/locale/eu.ts`:
- Around line 130-135: tour.previous uses "Aurrekoa" while carousel.prev uses
the variant "Aurretikoa", and tour.close uses "Saltatu" whereas other close keys
use "Itxi"; update for consistency by choosing the standard form "Aurrekoa" for
the carousel.prev entry (or change tour.previous to match if that was intended)
and change tour.close to "Itxi" if the intent is a literal "close" action;
locate the keys named tour.previous, carousel.prev and any other close keys
(e.g., close) in this locale file and make the string values consistent.

In `@src/runtime/locale/he.ts`:
- Around line 131-136: The tour translation uses "דלג" for the close action
which means "skip" and is inconsistent with other close labels; update the tour
object so its close property uses the same word as other components (change
tour.close from 'דלג' to 'סגור') to ensure consistent messaging across alert,
banner, modal, slideover, and toast.

In `@src/runtime/locale/hi.ts`:
- Around line 130-135: The translation for the locale object key tour.close is
inconsistent: replace the string 'छोड़ें' with 'बंद करें' to match other close
translations (e.g., alert.close, modal.close, toast.close, slideover.close) so
the tour.close semantic aligns with "close/dismiss"; if the intent was "skip"
instead, rename the key from close to skip and provide an appropriate 'छोड़ें'
translation for tour.skip to preserve clarity.

In `@src/runtime/locale/tr.ts`:
- Around line 130-135: The translation for tour.close is inconsistent: change
the value or the key so intent matches other translations; either rename the key
tour.close to tour.skip (and set its value to "Atla") if the button is meant to
"Skip" the tour, or keep the key tour.close and change its value from "Atla" to
"Kapat" to match other close keys like alert.close, banner.close, modal.close,
slideover.close, and toast.close; update any code references that use the
tour.close key if you rename it to tour.skip.

In `@src/runtime/locale/ug_cn.ts`:
- Around line 131-136: tour.close currently uses 'ئاتلاش' (skip) while other
close keys (alert.close, banner.close, modal.close, toast.close) use 'تاقاش';
replace the value of the tour.close property from 'ئاتلاش' to 'تاقاش' to make it
consistent, or if the intent is to offer a "skip" action, add a comment near the
tour object documenting that tour.close intentionally means "skip" and leave the
value as-is; locate the tour object and update the tour.close string
accordingly.

In `@src/runtime/locale/vi.ts`:
- Around line 130-135: tour.close is translated as "Bỏ qua" (Skip) but other
close keys use "Đóng"; update the translation for the tour object (tour.next,
tour.previous, tour.finish, tour.close) so that tour.close uses "Đóng" instead
of "Bỏ qua" to match the close semantics used by other components (unless the
tour action is intentionally a "skip", in which case leave as-is and add a
clarifying comment).

In `@src/runtime/locale/zh_tw.ts`:
- Around line 130-135: The translation for tour.close ('略過') is inconsistent
with other close keys; if the intent is to perform a generic "close" action,
change the value of tour.close to '關閉' to match other keys (update any
tests/usage if necessary), otherwise if the action is truly "skip tour" rename
the key tour.close to tour.skip and update every usage/site referencing
tour.close to tour.skip to keep semantics clear and consistent (search for
usages of "tour.close" to update callers).
🧹 Nitpick comments (1)
src/runtime/locale/th.ts (1)

130-135: Tour translations look good; minor note on close wording consistency.

tour.close is translated as "ข้าม" (skip), while every other component's close key (alert, banner, modal, slideover, toast) uses "ปิด" (close). If the intent is "skip the tour," this is a reasonable UX choice, but it diverges from the key's semantic name and the rest of the file. Consider whether "ปิด" would be more consistent, or if "ข้าม" is the deliberate choice here.

Comment thread src/runtime/locale/et.ts Outdated
Comment thread src/runtime/locale/eu.ts
Comment on lines +130 to 135
tour: {
next: 'Hurrengoa',
previous: 'Aurrekoa',
finish: 'Amaitu',
close: 'Saltatu'
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor inconsistency: tour.previous vs carousel.prev use different Basque words for "previous".

tour.previous is "Aurrekoa" (line 132) while carousel.prev is "Aurretikoa" (line 29). Both convey "previous" but differ in form. Consider aligning them — "Aurrekoa" is the more standard form, so the carousel entry may be the one to update in a follow-up.

Also, tour.close is "Saltatu" (Skip) whereas every other close key in this file uses "Itxi" (Close). If this is intentional (i.e., the tour close action semantically means "skip"), this is fine — just confirming it's a deliberate choice.

🤖 Prompt for AI Agents
In `@src/runtime/locale/eu.ts` around lines 130 - 135, tour.previous uses
"Aurrekoa" while carousel.prev uses the variant "Aurretikoa", and tour.close
uses "Saltatu" whereas other close keys use "Itxi"; update for consistency by
choosing the standard form "Aurrekoa" for the carousel.prev entry (or change
tour.previous to match if that was intended) and change tour.close to "Itxi" if
the intent is a literal "close" action; locate the keys named tour.previous,
carousel.prev and any other close keys (e.g., close) in this locale file and
make the string values consistent.

Comment thread src/runtime/locale/he.ts
Comment thread src/runtime/locale/hi.ts
Comment thread src/runtime/locale/tr.ts
Comment thread src/runtime/locale/ug_cn.ts
Comment thread src/runtime/locale/vi.ts
Comment thread src/runtime/locale/zh_tw.ts
@solidusite
Copy link
Copy Markdown
Contributor Author

solidusite commented Feb 14, 2026

I can't check the deploy error @benjamincanac , can you help me to understand the error?

I'm trying to deploy the repo inside my personal Vercel but I don't have the correct build command to use.

@benjamincanac
Copy link
Copy Markdown
Member

@solidusite It only misses the cover image for this new component, we'll add them with @HugoRCD once I find time to review this.

@benjamincanac benjamincanac changed the title feat(Tour): add guided tour component feat(Tour): new component Feb 15, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (6)
src/runtime/locale/hi.ts (1)

130-135: LGTM — prior inconsistency resolved.

close: 'बंद करें' now aligns with every other close key in the file (alert, banner, modal, slideover, toast).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/locale/hi.ts` around lines 130 - 135, The 'tour' object now uses
the 'close' key with value 'बंद करें', matching other components; no code change
needed—just confirm the 'tour' object (tour) contains keys next, previous,
finish, and close with the updated close value and commit as-is.
src/runtime/locale/he.ts (1)

131-136: LGTM — prior inconsistency resolved.

close: 'סגור' now aligns with every other close key in the file (alert, banner, modal, slideover, toast).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/locale/he.ts` around lines 131 - 136, The tour translation
already updates the 'close' key to 'סגור', resolving the prior inconsistency; no
code change is required—verify the 'tour' object in src/runtime/locale/he.ts
contains next/previous/finish/close with close: 'סגור' to match other
components' 'close' keys (alert, banner, modal, slideover, toast).
src/runtime/locale/ug_cn.ts (1)

131-136: LGTM — prior inconsistency resolved.

close: 'تاقاش' now aligns with every other close key in the file (alert, banner, commandPalette, modal, slideover, toast).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/locale/ug_cn.ts` around lines 131 - 136, The tour object's close
label is inconsistent with other components; update the tour.close property in
the tour object to 'تاقاش' (matching alert, banner, commandPalette, modal,
slideover, toast) and remove any duplicate or conflicting 'close' entries for
tour if present so there is a single consistent definition (inspect the tour
object and the 'close' key to locate and fix).
src/runtime/locale/et.ts (1)

130-135: LGTM – prior tour.close inconsistency ('Jäta vahele' → 'Sulge') is resolved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/locale/et.ts` around lines 130 - 135, The translation
inconsistency for the tour object has been resolved: ensure the property
tour.close in src/runtime/locale/et.ts uses 'Sulge' (replacing the prior 'Jäta
vahele'), confirm tour.next/previous/finish remain correct, and mark the change
approved—no further code changes required.
src/runtime/locale/zh_tw.ts (1)

130-135: LGTM – prior tour.close inconsistency ('略過' → '關閉') is resolved.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/locale/zh_tw.ts` around lines 130 - 135, The previous
inconsistency in the localization key tour.close has been corrected from '略過' to
'關閉'; no code changes required—confirm that the tour object (keys: next,
previous, finish, close) in zh_tw.ts uses the finalized translations
('下一步','上一步','完成','關閉') and proceed to approve/merge.
src/runtime/locale/sv.ts (1)

130-135: LGTM – prior untranslated-string issue is resolved.

All four keys are now proper Swedish and consistent with the rest of the locale (Stäng for close, Nästa/Föregående mirroring the carousel).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/locale/sv.ts` around lines 130 - 135, The translations in the
'tour' object (keys next, previous, finish, close) in src/runtime/locale/sv.ts
are correct and consistent with the locale, so no code changes are required —
approve the change as-is and ensure the 'tour' object remains: next: 'Nästa',
previous: 'Föregående', finish: 'Slutför', close: 'Stäng'.
🧹 Nitpick comments (5)
src/runtime/components/Tour.vue (5)

282-306: updateHighlight is scheduled twice per next()/prev() call.

Both next() (line 286) and prev() (line 304) explicitly call nextTick(updateHighlight). The watch(currentIndex, ...) on line 323 independently schedules the same call whenever currentIndex changes. Mutating currentIndex.value inside next()/prev() triggers the watcher, so two nextTick(updateHighlight) calls are enqueued per navigation.

♻️ Suggested simplification

Remove the redundant explicit calls inside the navigation functions and rely solely on the watcher:

 function next() {
   if (hasNext.value) {
     currentIndex.value += 1
     emits('next', currentIndex.value)
-    nextTick(updateHighlight)
     return
   }

   if (props.loop) {
     currentIndex.value = 0
     emits('next', currentIndex.value)
-    nextTick(updateHighlight)
     return
   }

   finish()
 }

 function prev() {
   if (hasPrev.value) {
     currentIndex.value -= 1
     emits('prev', currentIndex.value)
-    nextTick(updateHighlight)
   }
 }

Also applies to: 323-323

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 282 - 306, The navigation
functions next() and prev() are redundantly scheduling updateHighlight via
nextTick() while the watcher on currentIndex already enqueues
nextTick(updateHighlight) when currentIndex.value changes; remove the explicit
nextTick(updateHighlight) calls from next() and prev() (leave the
currentIndex.value mutations, emits('next'/'prev', ...) and loop/finish logic
intact) so updateHighlight is only scheduled by the watch(currentIndex, ...)
handler.

335-337: <span ref="fallbackRef"> is missing a data-slot attribute.

Per coding guidelines, all template elements in Vue components must carry a data-slot attribute.

♻️ Suggested fix
-      <span ref="fallbackRef" aria-hidden="true" />
+      <span ref="fallbackRef" data-slot="trigger" aria-hidden="true" />

As per coding guidelines: "Add data-slot="name" attributes on all template elements in Vue components."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 335 - 337, The <span
ref="fallbackRef" /> used inside Popover.Trigger in the Tour.vue template is
missing the required data-slot attribute; update that element (the span with
ref="fallbackRef" inside Popover.Trigger) to include a descriptive data-slot
value (e.g., data-slot="fallback" or another appropriate slot name per the
component semantics) so it complies with the coding guideline requiring
data-slot on all template elements.

96-101: finish() conflates tour dismissal with tour completion — add a separate close/cancel emit.

The close button (line 397, @click="finish") and the last-step Next button both call finish(), which emits the same 'finish' event. Consumers have no way to distinguish between "user completed all steps" and "user dismissed mid-tour." This limits analytics and conditional post-tour flows.

♻️ Suggested approach

Add a close event to TourEmits and call it exclusively from the close button:

 export interface TourEmits extends PopoverRootEmits {
   'update:step': [number]
   'next': [number]
   'prev': [number]
   'finish': []
+  'close': []
 }
 function finish() {
   open.value = false
   emits('finish')
 }

+function close() {
+  open.value = false
+  emits('close')
+}
-              `@click`="finish"
+              `@click`="close"

Also applies to: 308-311, 388-398

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 96 - 101, The Tour currently
uses finish() for both dismissal and completion, which prevents consumers from
distinguishing the two; add a separate 'close' emit to the TourEmits interface
and wherever the close button handler is wired (the close button that currently
calls finish, and any related handlers around the close/dismiss UI at the
regions noted), change that handler to emit 'close' instead of calling finish(),
leaving finish() reserved for true tour completion (e.g., the last-step Next
button); update any emits declarations/usages (TourEmits, the component emits
definition, and any methods or places that call finish() for dismissal) and
tests/consumers accordingly so dismissal emits 'close' and completion emits
'finish'.

217-218: eslint-disable comment masks a naming conflict between the ui prop and ui computed ref.

Rename the computed to avoid the collision and remove the suppression comment:

♻️ Suggested rename
-// eslint-disable-next-line vue/no-dupe-keys
-const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tour || {}) })({}))
+const uiConfig = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tour || {}) })({}))

Then replace all ui.xxx(...) calls in the template with uiConfig.xxx(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 217 - 218, The computed ref
named ui conflicts with the existing ui prop and the eslint-disable masks it;
rename the computed from ui to uiConfig (update the declaration: const uiConfig
= computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tour || {}) })({})))
remove the eslint-disable comment, and update all template and script usages
that call ui.xxx(...) to use uiConfig.xxx(...) instead so references point to
the new computed ref.

347-360: Hardcoded rgba(0, 0, 0, 0.5) for the spotlight overlay is not customizable.

When a target element is resolved, the overlay effect is produced via an inline boxShadow. This color is hardcoded and cannot be adjusted through props or the theme, unlike the full-page overlay div (lines 340-345) which uses ui.overlay(...).

♻️ Suggested approach

Expose an overlayColor prop (or CSS variable) and thread it through the inline style:

+  /**
+   * The backdrop color for the spotlight overlay effect.
+   * `@defaultValue` 'rgba(0, 0, 0, 0.5)'
+   */
+  overlayColor?: string
-          ...(showOverlay ? { boxShadow: '0 0 0 9999px rgba(0, 0, 0, 0.5)' } : {})
+          ...(showOverlay ? { boxShadow: `0 0 0 9999px ${props.overlayColor ?? 'rgba(0, 0, 0, 0.5)'}` } : {})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 347 - 360, The inline spotlight
uses a hardcoded boxShadow color; add a configurable overlay color (e.g., prop
overlayColor with a default like 'rgba(0, 0, 0, 0.5)' or read from a CSS
variable) and use it when composing the inline style for the highlight element:
update the component props to include overlayColor, and change the conditional
that builds boxShadow in the element that references highlightRect/showOverlay
to use that prop (instead of the literal 'rgba(0, 0, 0, 0.5)'); keep the
existing ui.highlight/uiProp?.highlight/currentStep?.ui usage intact so theming
still applies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/components/Tour.vue`:
- Around line 424-429: The ternary logic in the UButton rendering (using
hasNext, props.loop, currentStep?.nextLabel) wrongly prioritizes nextLabel over
finishLabel; change it so the decision is based only on whether there is a next
step or loop is enabled: if (hasNext || props.loop) render
(currentStep?.nextLabel || t('tour.next')), otherwise render
(currentStep?.finishLabel || t('tour.finish')). Update the expression around
currentStep?.nextLabel/currentStep?.finishLabel in the UButton (the same place
where hasNext and props.loop are checked) to implement this clear two-branch
conditional.
- Around line 231-249: The resolveTarget function is incorrectly auto-prefixing
plain strings with '#' which turns class names and complex selectors into
invalid ID selectors; remove the prefixing logic in resolveTarget so that when
typeof value === 'string' you treat value as a full CSS selector and pass it
straight to document.querySelector(value), and update the JSDoc for
TourStep.target to state that string values are interpreted as raw CSS selectors
(so callers must include '#' for IDs and '.' for classes or use other valid
selector syntax).

In `@src/runtime/locale/tj.ts`:
- Around line 128-129: The change to toast.close introduces a mismatch with
other Tajik "close" labels; revert toast.close back to 'Бастан' so it matches
alert.close, commandPalette.close, modal.close and slideover.close (or
alternatively, as a follow-up, change all of those keys to 'Пӯшидан' for a
unified term). Update the toast.close entry in the locale (the toast.close key)
to 'Бастан' to restore consistency with the other close-action strings.

---

Duplicate comments:
In `@src/runtime/locale/et.ts`:
- Around line 130-135: The translation inconsistency for the tour object has
been resolved: ensure the property tour.close in src/runtime/locale/et.ts uses
'Sulge' (replacing the prior 'Jäta vahele'), confirm tour.next/previous/finish
remain correct, and mark the change approved—no further code changes required.

In `@src/runtime/locale/he.ts`:
- Around line 131-136: The tour translation already updates the 'close' key to
'סגור', resolving the prior inconsistency; no code change is required—verify the
'tour' object in src/runtime/locale/he.ts contains next/previous/finish/close
with close: 'סגור' to match other components' 'close' keys (alert, banner,
modal, slideover, toast).

In `@src/runtime/locale/hi.ts`:
- Around line 130-135: The 'tour' object now uses the 'close' key with value
'बंद करें', matching other components; no code change needed—just confirm the
'tour' object (tour) contains keys next, previous, finish, and close with the
updated close value and commit as-is.

In `@src/runtime/locale/sv.ts`:
- Around line 130-135: The translations in the 'tour' object (keys next,
previous, finish, close) in src/runtime/locale/sv.ts are correct and consistent
with the locale, so no code changes are required — approve the change as-is and
ensure the 'tour' object remains: next: 'Nästa', previous: 'Föregående', finish:
'Slutför', close: 'Stäng'.

In `@src/runtime/locale/ug_cn.ts`:
- Around line 131-136: The tour object's close label is inconsistent with other
components; update the tour.close property in the tour object to 'تاقاش'
(matching alert, banner, commandPalette, modal, slideover, toast) and remove any
duplicate or conflicting 'close' entries for tour if present so there is a
single consistent definition (inspect the tour object and the 'close' key to
locate and fix).

In `@src/runtime/locale/zh_tw.ts`:
- Around line 130-135: The previous inconsistency in the localization key
tour.close has been corrected from '略過' to '關閉'; no code changes
required—confirm that the tour object (keys: next, previous, finish, close) in
zh_tw.ts uses the finalized translations ('下一步','上一步','完成','關閉') and proceed to
approve/merge.

---

Nitpick comments:
In `@src/runtime/components/Tour.vue`:
- Around line 282-306: The navigation functions next() and prev() are
redundantly scheduling updateHighlight via nextTick() while the watcher on
currentIndex already enqueues nextTick(updateHighlight) when currentIndex.value
changes; remove the explicit nextTick(updateHighlight) calls from next() and
prev() (leave the currentIndex.value mutations, emits('next'/'prev', ...) and
loop/finish logic intact) so updateHighlight is only scheduled by the
watch(currentIndex, ...) handler.
- Around line 335-337: The <span ref="fallbackRef" /> used inside
Popover.Trigger in the Tour.vue template is missing the required data-slot
attribute; update that element (the span with ref="fallbackRef" inside
Popover.Trigger) to include a descriptive data-slot value (e.g.,
data-slot="fallback" or another appropriate slot name per the component
semantics) so it complies with the coding guideline requiring data-slot on all
template elements.
- Around line 96-101: The Tour currently uses finish() for both dismissal and
completion, which prevents consumers from distinguishing the two; add a separate
'close' emit to the TourEmits interface and wherever the close button handler is
wired (the close button that currently calls finish, and any related handlers
around the close/dismiss UI at the regions noted), change that handler to emit
'close' instead of calling finish(), leaving finish() reserved for true tour
completion (e.g., the last-step Next button); update any emits
declarations/usages (TourEmits, the component emits definition, and any methods
or places that call finish() for dismissal) and tests/consumers accordingly so
dismissal emits 'close' and completion emits 'finish'.
- Around line 217-218: The computed ref named ui conflicts with the existing ui
prop and the eslint-disable masks it; rename the computed from ui to uiConfig
(update the declaration: const uiConfig = computed(() => tv({ extend: tv(theme),
...(appConfig.ui?.tour || {}) })({}))) remove the eslint-disable comment, and
update all template and script usages that call ui.xxx(...) to use
uiConfig.xxx(...) instead so references point to the new computed ref.
- Around line 347-360: The inline spotlight uses a hardcoded boxShadow color;
add a configurable overlay color (e.g., prop overlayColor with a default like
'rgba(0, 0, 0, 0.5)' or read from a CSS variable) and use it when composing the
inline style for the highlight element: update the component props to include
overlayColor, and change the conditional that builds boxShadow in the element
that references highlightRect/showOverlay to use that prop (instead of the
literal 'rgba(0, 0, 0, 0.5)'); keep the existing
ui.highlight/uiProp?.highlight/currentStep?.ui usage intact so theming still
applies.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8dbd1d9 and de565f7.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Tour-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Tour.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (64)
  • playgrounds/nuxt/app/pages/components/tour.vue
  • src/runtime/components/Tour.vue
  • src/runtime/locale/ar.ts
  • src/runtime/locale/az.ts
  • src/runtime/locale/be.ts
  • src/runtime/locale/bg.ts
  • src/runtime/locale/bn.ts
  • src/runtime/locale/ca.ts
  • src/runtime/locale/ckb.ts
  • src/runtime/locale/cs.ts
  • src/runtime/locale/da.ts
  • src/runtime/locale/de.ts
  • src/runtime/locale/de_ch.ts
  • src/runtime/locale/el.ts
  • src/runtime/locale/en.ts
  • src/runtime/locale/es.ts
  • src/runtime/locale/et.ts
  • src/runtime/locale/eu.ts
  • src/runtime/locale/fa_ir.ts
  • src/runtime/locale/fi.ts
  • src/runtime/locale/fr.ts
  • src/runtime/locale/gl.ts
  • src/runtime/locale/he.ts
  • src/runtime/locale/hi.ts
  • src/runtime/locale/hr.ts
  • src/runtime/locale/hu.ts
  • src/runtime/locale/hy.ts
  • src/runtime/locale/id.ts
  • src/runtime/locale/it.ts
  • src/runtime/locale/ja.ts
  • src/runtime/locale/ka.ts
  • src/runtime/locale/kk.ts
  • src/runtime/locale/km.ts
  • src/runtime/locale/ko.ts
  • src/runtime/locale/ky.ts
  • src/runtime/locale/lb.ts
  • src/runtime/locale/lo.ts
  • src/runtime/locale/lt.ts
  • src/runtime/locale/mn.ts
  • src/runtime/locale/ms.ts
  • src/runtime/locale/nb_no.ts
  • src/runtime/locale/nl.ts
  • src/runtime/locale/pl.ts
  • src/runtime/locale/pt.ts
  • src/runtime/locale/pt_br.ts
  • src/runtime/locale/ro.ts
  • src/runtime/locale/ru.ts
  • src/runtime/locale/sk.ts
  • src/runtime/locale/sl.ts
  • src/runtime/locale/sq.ts
  • src/runtime/locale/sv.ts
  • src/runtime/locale/th.ts
  • src/runtime/locale/tj.ts
  • src/runtime/locale/tr.ts
  • src/runtime/locale/ug_cn.ts
  • src/runtime/locale/uk.ts
  • src/runtime/locale/ur.ts
  • src/runtime/locale/uz.ts
  • src/runtime/locale/vi.ts
  • src/runtime/locale/zh_cn.ts
  • src/runtime/locale/zh_tw.ts
  • src/runtime/types/index.ts
  • src/runtime/utils/locale.ts
  • test/components/Tour.spec.ts
🚧 Files skipped from review as they are similar to previous changes (33)
  • src/runtime/locale/ru.ts
  • src/runtime/locale/ms.ts
  • src/runtime/locale/sq.ts
  • src/runtime/locale/nl.ts
  • src/runtime/locale/nb_no.ts
  • src/runtime/locale/hu.ts
  • src/runtime/locale/pl.ts
  • src/runtime/locale/fi.ts
  • src/runtime/locale/de_ch.ts
  • src/runtime/locale/ky.ts
  • src/runtime/locale/ro.ts
  • src/runtime/locale/gl.ts
  • src/runtime/locale/uz.ts
  • src/runtime/locale/bg.ts
  • src/runtime/locale/ko.ts
  • src/runtime/locale/id.ts
  • src/runtime/locale/hr.ts
  • src/runtime/locale/lt.ts
  • src/runtime/locale/bn.ts
  • src/runtime/locale/sk.ts
  • src/runtime/locale/be.ts
  • src/runtime/locale/en.ts
  • src/runtime/locale/az.ts
  • src/runtime/locale/ckb.ts
  • playgrounds/nuxt/app/pages/components/tour.vue
  • src/runtime/locale/ar.ts
  • src/runtime/locale/it.ts
  • src/runtime/locale/ur.ts
  • src/runtime/locale/lb.ts
  • src/runtime/locale/kk.ts
  • src/runtime/locale/pt.ts
  • src/runtime/locale/ka.ts
  • src/runtime/locale/eu.ts

Comment on lines +231 to +249
function resolveTarget(target?: TourTarget): ReferenceElement | null | undefined {
if (!target || typeof window === 'undefined') {
return undefined
}

const value = typeof target === 'function' ? target() : target

if (!value) {
return undefined
}

if (typeof value === 'string') {
const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`

return document.querySelector(selector) as ReferenceElement | null
}

return value
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

resolveTarget silently auto-prefixes plain strings with #, turning class names into broken ID selectors.

A caller passing "my-class" (intending .my-class) gets document.querySelector("#my-class") instead. Complex selectors like "div > .foo" or "[data-id='bar']" are also mishandled by the prefix logic.

🐛 Suggested fix
-    const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`
+    const selector = value.startsWith('#') || value.startsWith('.') || value.startsWith('[') ? value : `#${value}`

Additionally, consider documenting in TourStep.target's JSDoc that bare strings without a # or . prefix are treated as IDs.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function resolveTarget(target?: TourTarget): ReferenceElement | null | undefined {
if (!target || typeof window === 'undefined') {
return undefined
}
const value = typeof target === 'function' ? target() : target
if (!value) {
return undefined
}
if (typeof value === 'string') {
const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`
return document.querySelector(selector) as ReferenceElement | null
}
return value
}
function resolveTarget(target?: TourTarget): ReferenceElement | null | undefined {
if (!target || typeof window === 'undefined') {
return undefined
}
const value = typeof target === 'function' ? target() : target
if (!value) {
return undefined
}
if (typeof value === 'string') {
const selector = value.startsWith('#') || value.startsWith('.') || value.startsWith('[') ? value : `#${value}`
return document.querySelector(selector) as ReferenceElement | null
}
return value
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 231 - 249, The resolveTarget
function is incorrectly auto-prefixing plain strings with '#' which turns class
names and complex selectors into invalid ID selectors; remove the prefixing
logic in resolveTarget so that when typeof value === 'string' you treat value as
a full CSS selector and pass it straight to document.querySelector(value), and
update the JSDoc for TourStep.target to state that string values are interpreted
as raw CSS selectors (so callers must include '#' for IDs and '.' for classes or
use other valid selector syntax).

Comment on lines +424 to +429
@click="next"
>
{{ hasNext || props.loop || currentStep?.nextLabel
? (currentStep?.nextLabel || t('tour.next'))
: (currentStep?.finishLabel || t('tour.finish')) }}
</UButton>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

currentStep?.nextLabel in the condition silently swallows finishLabel on the last step.

The current guard hasNext || props.loop || currentStep?.nextLabel means:

  • When loop is true, the button always shows nextLabel || 'Next'finishLabel is always ignored.
  • When on the last step (no hasNext, no loop) but nextLabel is set, the button shows nextLabel instead of finishLabel || 'Finish'.

Callers who set both nextLabel and finishLabel on the same step will find finishLabel has no effect.

🐛 Suggested fix
-                {{ hasNext || props.loop || currentStep?.nextLabel
-                  ? (currentStep?.nextLabel || t('tour.next'))
-                  : (currentStep?.finishLabel || t('tour.finish')) }}
+                {{ hasNext || props.loop
+                  ? (currentStep?.nextLabel || t('tour.next'))
+                  : (currentStep?.finishLabel || t('tour.finish')) }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@click="next"
>
{{ hasNext || props.loop || currentStep?.nextLabel
? (currentStep?.nextLabel || t('tour.next'))
: (currentStep?.finishLabel || t('tour.finish')) }}
</UButton>
`@click`="next"
>
{{ hasNext || props.loop
? (currentStep?.nextLabel || t('tour.next'))
: (currentStep?.finishLabel || t('tour.finish')) }}
</UButton>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 424 - 429, The ternary logic in
the UButton rendering (using hasNext, props.loop, currentStep?.nextLabel)
wrongly prioritizes nextLabel over finishLabel; change it so the decision is
based only on whether there is a next step or loop is enabled: if (hasNext ||
props.loop) render (currentStep?.nextLabel || t('tour.next')), otherwise render
(currentStep?.finishLabel || t('tour.finish')). Update the expression around
currentStep?.nextLabel/currentStep?.finishLabel in the UButton (the same place
where hasNext and props.loop are checked) to implement this clear two-branch
conditional.

Comment thread src/runtime/locale/tj.ts
Comment on lines +128 to +129
close: 'Пӯшидан'
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

toast.close change widens an existing inconsistency in Tajik "close" translations.

The Tajik locale already had two different words for "close" across components. Changing toast.close from 'Бастан' to 'Пӯшидан' to align with the new tour.close deepens that inconsistency rather than resolving it — alert.close, commandPalette.close, modal.close, and slideover.close still use 'Бастан'.

Consider either reverting toast.close to 'Бастан' to keep it consistent with the other close-action strings, or as a follow-up, aligning all close labels to a single word.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/locale/tj.ts` around lines 128 - 129, The change to toast.close
introduces a mismatch with other Tajik "close" labels; revert toast.close back
to 'Бастан' so it matches alert.close, commandPalette.close, modal.close and
slideover.close (or alternatively, as a follow-up, change all of those keys to
'Пӯшидан' for a unified term). Update the toast.close entry in the locale (the
toast.close key) to 'Бастан' to restore consistency with the other close-action
strings.

solidusite and others added 7 commits March 7, 2026 14:11
Add an `overlay` option that darkens the rest of the page while keeping
the highlighted target element and the tour popover visible. Uses an
inline box-shadow spread on the highlight div for steps with a target,
and a fullscreen fallback div for steps without one. The prop can be set
globally or overridden per-step.

- Add `overlay` slot to theme for the fullscreen fallback
- Add `overlay` prop to TourStep and TourProps
- Set z-index on Popover.Content when overlay is active so it renders
  above the shadow (reka-ui propagates it to the wrapper)
- Add overlay example, docs section, playground toggle, and Tour entry
  in the playground navigation
- Add missing `tour` key to Belarusian locale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Translate next, previous, finish and close labels for the Tour
component across all 57 non-English locale files. Translations follow
each locale's existing conventions (e.g. reusing carousel.next/prev
terms where applicable).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactor the inline styles of the popover content in Tour component snapshots to ensure consistent rendering. This change improves the clarity and maintainability of the snapshot tests by standardizing the style attributes across different test cases.
@solidusite solidusite force-pushed the feat/tour-component branch from de565f7 to 9369ac3 Compare March 7, 2026 13:11
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/content/docs/2.components/tour.md`:
- Around line 21-25: The docs currently list only selector strings and ref
functions for the step `target`, but the component's public type TourTarget also
accepts a direct ReferenceElement (an HTMLElement); update the text in the tour
documentation to include this third option and show an example like "a direct
HTMLElement (e.g., document.getElementById('foo') or a ReferenceElement)".
Mention TourTarget and the `target` property so readers can map the docs to the
component API.

In `@playgrounds/nuxt/app/pages/components/tour.vue`:
- Around line 40-41: The "Start tour" button currently only sets open = true and
doesn't reset the tour index, so reopenings resume the previous step; update the
click handler (or create a small method like startTour) to set step = 0 before
setting open = true (referencing the reactive vars step and open in this
component) so the demo always starts from step 0 when started again.

In `@src/runtime/components/Tour.vue`:
- Around line 186-190: The code currently uses fallbackRef (hidden <span>) as
the resolved target for `reference`, causing `updateHighlight()` to compute a
tiny `highlightRect` and spotlight the placeholder; change the logic so
`resolveTarget(target)` can return undefined and `reference` only falls back to
`fallbackRef` for popover positioning (not for the highlight). Specifically, in
the places around `currentStep.value?.target` / `resolveTarget(target)` (and the
other affected blocks at the indicated ranges), stop passing the hidden fallback
into the routine that computes `highlightRect`—leave `highlightRect` unset/null
when `resolveTarget` returns no real element, and only use `fallbackRef` when
computing popover position. Ensure `updateHighlight()` checks for a real
resolved element before computing/setting `highlightRect`.
- Around line 276-280: The tour currently only recomputes highlight geometry
(e.g., in goTo and the other step-change handlers) which can leave the target
off-screen; add an opt-in prop (e.g., autoScrollToTarget: boolean) and when
resolving the target element in goTo, next/prev/finish handlers, call
element.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth"
}) or a polyfilled scrollIntoViewIfNeeded before invoking updateHighlight
(ensure the scroll completes or await a small nextTick if needed), gated by the
new prop so scrolling is optional; update the handlers (goTo, the step
navigation functions referenced around those ranges) to perform the
scroll-to-target step prior to calling updateHighlight.
- Around line 242-245: The selector resolution currently calls
document.querySelector(selector) which will throw on invalid CSS selectors; wrap
that call in a try/catch inside the method that converts a string target to an
element (the block that checks typeof value === 'string' and builds const
selector) and on catch return null so the tour degrades to the no-target path
instead of crashing; keep the existing logic of normalizing '#' prefix and only
handle the error around document.querySelector to log/debug if desired and
return null.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a65c35f-c5e8-47fc-ba85-870d505d12f2

📥 Commits

Reviewing files that changed from the base of the PR and between de565f7 and 9369ac3.

📒 Files selected for processing (9)
  • docs/app/components/content/examples/tour/TourBasicExample.vue
  • docs/app/components/content/examples/tour/TourCustomLabelsExample.vue
  • docs/app/components/content/examples/tour/TourLoopExample.vue
  • docs/app/components/content/examples/tour/TourModalExample.vue
  • docs/app/components/content/examples/tour/TourOverlayExample.vue
  • docs/content/docs/2.components/tour.md
  • playgrounds/nuxt/app/composables/useNavigation.ts
  • playgrounds/nuxt/app/pages/components/tour.vue
  • src/runtime/components/Tour.vue
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/app/components/content/examples/tour/TourOverlayExample.vue
  • docs/app/components/content/examples/tour/TourCustomLabelsExample.vue
  • docs/app/components/content/examples/tour/TourLoopExample.vue
  • docs/app/components/content/examples/tour/TourModalExample.vue

Comment on lines +53 to +55
<UButton @click="open = true">
Start Tour
</UButton>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Restart the example tour from the first step.

This button only reopens the tour. If a reader closes it on step 2 or 3, “Start Tour” resumes there instead of showing the full example again.

💡 Proposed fix
-    <UButton `@click`="open = true">
+    <UButton `@click`="step = 0; open = true">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<UButton @click="open = true">
Start Tour
</UButton>
<UButton `@click`="step = 0; open = true">
Start Tour
</UButton>

Comment on lines +21 to +25
Each step in the tour requires a `target` property that can be:
- A string ID selector (e.g., `'#my-element'`)
- A CSS class selector (e.g., `'.my-class'`)
- A ref function that returns an HTMLElement (e.g., `() => myRef.value`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Document direct element targets too.

TourTarget also accepts a ReferenceElement directly, but this list only mentions selector strings and functions. The docs should cover that input so the public API matches the component. Based on learnings, "Update documentation if applicable when making changes to components".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/docs/2.components/tour.md` around lines 21 - 25, The docs
currently list only selector strings and ref functions for the step `target`,
but the component's public type TourTarget also accepts a direct
ReferenceElement (an HTMLElement); update the text in the tour documentation to
include this third option and show an example like "a direct HTMLElement (e.g.,
document.getElementById('foo') or a ReferenceElement)". Mention TourTarget and
the `target` property so readers can map the docs to the component API.

Comment on lines +40 to +41
<UButton icon="i-lucide-wand-2" color="primary" variant="soft" @click="open = true">
Start tour
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reset the demo to step 0 when starting again.

Clicking “Start tour” after finishing or closing mid-flow reopens the tour on the previous step because step is never reset.

💡 Proposed fix
-    <UButton icon="i-lucide-wand-2" color="primary" variant="soft" `@click`="open = true">
+    <UButton icon="i-lucide-wand-2" color="primary" variant="soft" `@click`="step = 0; open = true">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<UButton icon="i-lucide-wand-2" color="primary" variant="soft" @click="open = true">
Start tour
<UButton icon="i-lucide-wand-2" color="primary" variant="soft" `@click`="step = 0; open = true">
Start tour
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playgrounds/nuxt/app/pages/components/tour.vue` around lines 40 - 41, The
"Start tour" button currently only sets open = true and doesn't reset the tour
index, so reopenings resume the previous step; update the click handler (or
create a small method like startTour) to set step = 0 before setting open = true
(referencing the reactive vars step and open in this component) so the demo
always starts from step 0 when started again.

Comment on lines +186 to +190
const target = currentStep.value?.target
const resolved = resolveTarget(target)

return resolved ?? fallbackRef.value ?? undefined
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't build the spotlight from fallbackRef.

When a step target does not resolve, reference falls back to the hidden <span>, so updateHighlight() still produces a tiny rect and the overlay spotlights that placeholder instead of showing the fullscreen fallback overlay. Keep the fallback only for popover positioning, and leave highlightRect unset when there is no real target.

Also applies to: 255-273, 341-358

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 186 - 190, The code currently
uses fallbackRef (hidden <span>) as the resolved target for `reference`, causing
`updateHighlight()` to compute a tiny `highlightRect` and spotlight the
placeholder; change the logic so `resolveTarget(target)` can return undefined
and `reference` only falls back to `fallbackRef` for popover positioning (not
for the highlight). Specifically, in the places around
`currentStep.value?.target` / `resolveTarget(target)` (and the other affected
blocks at the indicated ranges), stop passing the hidden fallback into the
routine that computes `highlightRect`—leave `highlightRect` unset/null when
`resolveTarget` returns no real element, and only use `fallbackRef` when
computing popover position. Ensure `updateHighlight()` checks for a real
resolved element before computing/setting `highlightRect`.

Comment on lines +242 to +245
if (typeof value === 'string') {
const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`

return document.querySelector(selector) as ReferenceElement | null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Catch malformed selector strings here.

document.querySelector() throws on invalid CSS selectors, and target is caller-provided data. A single bad step will crash the tour instead of degrading to the no-target path.

🐛 Proposed fix
   if (typeof value === 'string') {
     const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`
-
-    return document.querySelector(selector) as ReferenceElement | null
+    try {
+      return document.querySelector(selector) as ReferenceElement | null
+    } catch {
+      return null
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof value === 'string') {
const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`
return document.querySelector(selector) as ReferenceElement | null
if (typeof value === 'string') {
const selector = value.startsWith('#') || value.startsWith('.') ? value : `#${value}`
try {
return document.querySelector(selector) as ReferenceElement | null
} catch {
return null
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 242 - 245, The selector
resolution currently calls document.querySelector(selector) which will throw on
invalid CSS selectors; wrap that call in a try/catch inside the method that
converts a string target to an element (the block that checks typeof value ===
'string' and builds const selector) and on catch return null so the tour
degrades to the no-target path instead of crashing; keep the existing logic of
normalizing '#' prefix and only handle the error around document.querySelector
to log/debug if desired and return null.

Comment on lines +276 to +280
function goTo(index: number) {
currentIndex.value = index
open.value = true
nextTick(updateHighlight)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Step transitions need to bring the target into view.

These code paths only recompute geometry. If the next target is below the fold or inside a scrollable container, the popover/highlight can open completely off-screen, which breaks the tour flow. Scroll the resolved target into view before updateHighlight() runs, ideally behind an opt-in prop.

Also applies to: 282-294, 300-304, 313-323

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Tour.vue` around lines 276 - 280, The tour currently
only recomputes highlight geometry (e.g., in goTo and the other step-change
handlers) which can leave the target off-screen; add an opt-in prop (e.g.,
autoScrollToTarget: boolean) and when resolving the target element in goTo,
next/prev/finish handlers, call element.scrollIntoView({ block: "nearest",
inline: "nearest", behavior: "smooth" }) or a polyfilled scrollIntoViewIfNeeded
before invoking updateHighlight (ensure the scroll completes or await a small
nextTick if needed), gated by the new prop so scrolling is optional; update the
handlers (goTo, the step navigation functions referenced around those ranges) to
perform the scroll-to-target step prior to calling updateHighlight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interactive Feature Tour Component

4 participants