Skip to content

feat(useTour): new composable#6557

Merged
benjamincanac merged 2 commits into
v4from
feat/use-tour
Jun 5, 2026
Merged

feat(useTour): new composable#6557
benjamincanac merged 2 commits into
v4from
feat/use-tour

Conversation

@benjamincanac

@benjamincanac benjamincanac commented Jun 4, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

Resolves #4874

Lightweight alternative to #5781 (full Tour component). Instead of a new component with its own theme, ~65 locale files and snapshots, a tour is a useTour composable driving a single re-anchored <UPopover>.

❓ 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

This adds a small useTour composable and makes Popover's reference prop a first-class, reactive anchoring input — together they cover the guided-tour use case (#4874) without a dedicated component.

useTour(steps, options?) owns the step state and resolves each step's target into a reference:

const tour = useTour([
  { target: '#cta', title: 'Get started' },
  { target: () => card.value, title: 'Profile', side: 'right' },
  { target: null, title: 'All set' } // centered
])
<UPopover :open="tour.open.value" :reference="tour.reference.value" :dismissible="false">
  <template #content><!-- your content + Back/Next buttons --></template>
</UPopover>

target accepts a CSS selector, an element, a virtual element (anything with getBoundingClientRect), a ref/getter, or null to center in the viewport. Every other field on a step is passed through and available via current, so content and navigation stay in userland — no extra theme or locale to maintain. Returns open, index, current, reference, total, hasNext, hasPrev, start, next, prev, goTo, finish.

Popover change: reference is now forwarded to the content (PopperContent positions against reference ?? rootContext.anchor) instead of the trigger. This:

  • makes the public reference prop work for click popovers (previously it only worked via Vue attribute fallthrough through the trigger), and uniformly for hover;
  • re-anchors reactively when the active step changes;
  • renders no placeholder element when there's no trigger (the trigger now renders only when a default slot is provided).

The public <UPopover :reference> API is unchanged. All existing :reference consumers (cursor examples, InputDate calendars anchored to the input, table hover popover) keep the same behavior — verified equivalent and covered by tests with unchanged snapshots.

📝 Checklist

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

Add a `useTour` composable that drives a guided tour with one `<UPopover>`
whose anchor moves between steps, instead of a dedicated Tour component.
The composable owns the step state and resolves each step's `target`
(selector, element, virtual element, ref or getter, or `null` to center)
into a `reference` bound to `<UPopover :reference>`. Content and navigation
stay in userland, so there is no extra theme or locale to maintain.

To support this, `Popover` now forwards `reference` to the content
(`PopperContent` positions against `reference ?? rootContext.anchor`)
rather than the trigger. This makes the public `reference` prop work for
click popovers without relying on attribute fallthrough, re-anchors
reactively, and renders no placeholder element when there is no trigger.
The trigger now renders only when a default slot is provided.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the v4 #4488 label Jun 4, 2026
@benjamincanac benjamincanac mentioned this pull request Jun 4, 2026
10 tasks
@pkg-pr-new

pkg-pr-new Bot commented Jun 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@nuxt/ui@6557

commit: 5a70fc2

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a guided tour feature to Nuxt UI through a new useTour composable that drives a single moving UPopover anchor across tour steps. The composable manages reactive tour state (open/index), resolves step targets into DOM elements or virtual elements, automatically scrolls targets into view, and provides navigation methods with optional looping. The Popover component was updated to accept explicit external reference binding without requiring a trigger slot, enabling composables like useTour to position content independently. The feature is wired into public exports and includes comprehensive test coverage and documentation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR provides a guided tour solution via useTour composable with step targeting and reactive reference positioning. While it doesn't include built-in highlight styling or dedicated theming, it achieves the core requirement of offering a native Nuxt UI tour mechanism (#4874).
Out of Scope Changes check ✅ Passed All changes are scoped to tour functionality (useTour composable, Popover reference anchoring) and supporting documentation/tests. The Popover modification to forward reference is a necessary enabling change directly tied to the tour feature.
Description check ✅ Passed The PR description clearly describes the implementation of a useTour composable and related Popover changes to enable guided tours, with detailed examples and explanations.
Title check ✅ Passed The title 'feat(useTour): new composable' is directly related to the main change—adding a new useTour composable for guided tours.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/use-tour

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/composables/useTour.spec.ts (1)

10-156: ⚡ Quick win

Add regression tests for empty-step start and invalid selector targets.

Given the runtime edge cases in useTour, add tests that assert:

  1. start()/goTo() keep open=false when steps is empty, and
  2. malformed selector targets do not throw and resolve to undefined.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/composables/useTour.spec.ts` around lines 10 - 156, Add two regression
tests for useTour: (1) verify that when the steps array is empty, calling
start() and goTo(...) does not open the tour (open.value remains false) and
total.value stays 0; (2) verify that a malformed or non-matching selector target
(e.g., '`#no-such-id`' or an invalid selector string) does not throw and results
in tour.reference.value being undefined after start(); place tests alongside the
existing suites referencing useTour, start, goTo, steps, open, total, and
reference to locate the correct test file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/components/Popover.vue`:
- Around line 125-127: The Component.Trigger wrapper in Popover.vue is missing
the required slot marker; update the <Component.Trigger> element (used where
slots.default is rendered) to include data-slot="trigger" so the trigger wrapper
becomes <Component.Trigger ... data-slot="trigger"> ensuring slot consistency
for the popover's trigger slot.

In `@src/runtime/composables/useTour.ts`:
- Around line 144-147: The goTo function currently sets index.value and
unconditionally sets open.value = true which can leave the tour open with no
steps; update goTo (and any other functions that set index and open like the one
referenced at lines 149-151) to only set open.value = true when total.value > 0
(e.g., set index.value = value as before but wrap open.value = true in a guard
if total.value > 0), ensuring the tour is not opened when there are zero steps.
- Around line 126-129: The selector resolution in the reference computation may
throw for malformed selectors when evaluating document.querySelector(selector);
update the logic in useTour's reference resolution (the branch handling typeof
target === 'string' that builds selector) to guard against exceptions by
wrapping the querySelector call in a try/catch and returning undefined on any
DOMException or other errors; keep the selector construction (target.startsWith
checks) the same but ensure errors from document.querySelector(selector) are
swallowed and result in undefined rather than letting the exception bubble.

---

Nitpick comments:
In `@test/composables/useTour.spec.ts`:
- Around line 10-156: Add two regression tests for useTour: (1) verify that when
the steps array is empty, calling start() and goTo(...) does not open the tour
(open.value remains false) and total.value stays 0; (2) verify that a malformed
or non-matching selector target (e.g., '`#no-such-id`' or an invalid selector
string) does not throw and results in tour.reference.value being undefined after
start(); place tests alongside the existing suites referencing useTour, start,
goTo, steps, open, total, and reference to locate the correct test file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6e82eaa-c305-4180-b472-6daef83d0333

📥 Commits

Reviewing files that changed from the base of the PR and between 71b9001 and 00e8370.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Popover-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Popover.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • docs/app/components/content/examples/use-tour/UseTourExample.vue
  • docs/content/docs/3.composables/use-tour.md
  • src/imports.ts
  • src/runtime/components/Popover.vue
  • src/runtime/composables/index.ts
  • src/runtime/composables/useTour.ts
  • test/components/Popover.spec.ts
  • test/composables/useTour.spec.ts

Comment thread src/runtime/components/Popover.vue
Comment thread src/runtime/composables/useTour.ts
Comment thread src/runtime/composables/useTour.ts
- `goTo` no longer opens the tour when there are no steps (the `total`
  watcher only reacts to changes, so it can't catch a tour started while
  already empty)
- swallow `querySelector` errors from malformed selectors and leave the
  step unanchored instead of throwing inside the `reference` computed

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@benjamincanac benjamincanac changed the title feat(useTour): guided tours via a single re-anchored popover feat(useTour): new composable Jun 5, 2026
@benjamincanac
benjamincanac merged commit dc05151 into v4 Jun 5, 2026
24 checks passed
@benjamincanac
benjamincanac deleted the feat/use-tour branch June 5, 2026 14:54
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

1 participant