feat(useTour): new composable#6557
Conversation
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>
commit: |
📝 WalkthroughWalkthroughThis PR adds a guided tour feature to Nuxt UI through a new Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
test/composables/useTour.spec.ts (1)
10-156: ⚡ Quick winAdd regression tests for empty-step start and invalid selector targets.
Given the runtime edge cases in
useTour, add tests that assert:
start()/goTo()keepopen=falsewhenstepsis empty, and- 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
⛔ Files ignored due to path filters (2)
test/components/__snapshots__/Popover-vue.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Popover.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (8)
docs/app/components/content/examples/use-tour/UseTourExample.vuedocs/content/docs/3.composables/use-tour.mdsrc/imports.tssrc/runtime/components/Popover.vuesrc/runtime/composables/index.tssrc/runtime/composables/useTour.tstest/components/Popover.spec.tstest/composables/useTour.spec.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>
🔗 Linked issue
Resolves #4874
Lightweight alternative to #5781 (full
Tourcomponent). Instead of a new component with its own theme, ~65 locale files and snapshots, a tour is auseTourcomposable driving a single re-anchored<UPopover>.❓ Type of change
📚 Description
This adds a small
useTourcomposable and makesPopover'sreferenceprop 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'stargetinto areference:targetaccepts a CSS selector, an element, a virtual element (anything withgetBoundingClientRect), a ref/getter, ornullto center in the viewport. Every other field on a step is passed through and available viacurrent, so content and navigation stay in userland — no extra theme or locale to maintain. Returnsopen,index,current,reference,total,hasNext,hasPrev,start,next,prev,goTo,finish.Popover change:
referenceis now forwarded to the content (PopperContentpositions againstreference ?? rootContext.anchor) instead of the trigger. This:referenceprop work for click popovers (previously it only worked via Vue attribute fallthrough through the trigger), and uniformly for hover;The public
<UPopover :reference>API is unchanged. All existing:referenceconsumers (cursor examples,InputDatecalendars anchored to the input, table hover popover) keep the same behavior — verified equivalent and covered by tests with unchanged snapshots.📝 Checklist