feat: Guided Tours Custom Navigation#2340
Conversation
🎩 PreviewA preview build has been created at: |
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
7f83eb8 to
9899ee2
Compare
84f39e0 to
d6d13ae
Compare
9899ee2 to
a773cce
Compare
d6d13ae to
e97b036
Compare
a773cce to
b9acf61
Compare
e97b036 to
d308ae7
Compare
9b16f8f to
fadf84a
Compare
95b9b2a to
01da4c7
Compare
fadf84a to
5cd15ef
Compare
01da4c7 to
d9c0e95
Compare
9e7059e to
7753fca
Compare
07b002a to
2262436
Compare
7753fca to
89a72fe
Compare
2262436 to
d83a715
Compare
89a72fe to
aa3fba7
Compare
d83a715 to
dd40a33
Compare
aa3fba7 to
febd689
Compare
dd40a33 to
47cfe52
Compare
febd689 to
68175d1
Compare
47cfe52 to
606887b
Compare
68175d1 to
aaa1a46
Compare
606887b to
d29cf67
Compare
6b7ed93 to
af8b204
Compare
d29cf67 to
5455cd2
Compare
af8b204 to
7938fe4
Compare
| targetSearchTerm?: string; | ||
| targetArgumentName?: string; | ||
| targetMinCount?: number; | ||
| }; |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Duplicated step-target type shape (project-conventions — extract duplicated definitions)
This ChecklistStep enumerates the same target* fields as TourActionLabelInput in tourActionLabels.ts, and tourActionLabel(interactiveStep) is already called with a ChecklistStep. The two will drift as new interaction targets are added. Both files are new in this PR, so it's the right time to unify.
Fix: type ChecklistStep = StepType & TourActionLabelInput; (import TourActionLabelInput) — single source of truth for the target fields.
| const Button = props.Button as FC<PropsWithChildren<NavButtonProps>>; | ||
|
|
||
| const hiddenPlaceholder = ( | ||
| <span aria-hidden style={{ visibility: "hidden", pointerEvents: "none" }}> |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Inline static style (project-conventions — inline style only for dynamic/computed values)
style={{ visibility: "hidden", pointerEvents: "none" }} is fully static → use Tailwind: className="invisible pointer-events-none". Carried over from the old renderNextButton, but this file is new/extracted here so it's fair to fix now.
| <InlineStack | ||
| gap="2" | ||
| blockAlign="center" | ||
| className="rounded-md bg-slate-50 px-3 py-2" |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Hardcoded palette colors vs tone/tokens (ui-primitives)
bg-slate-50 here and text-zinc-400 / text-zinc-500 line-through / text-green-600 on the checklist Icon/Text use raw palette classes. Prefer the semantic equivalents: the checklist Text → tone="subdued", bg-slate-50 → bg-muted. (The text-blue-*/text-zinc-* on the dot Badge variant="dot" below are decorative state colors — leave those if no token fits.)
| recordTourVisited(currentStep); | ||
| const visited = getTourVisitedMax(); | ||
|
|
||
| const NavButton: FC<PropsWithChildren<NavButtonProps>> = ({ |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Component defined inside render (react-patterns)
NavButton is declared in TourNavigation's render body, so it gets a new component identity every render and the nav buttons remount each time. Low impact here (stateless leaves, and reactour's render-prop API nudges this shape), but hoisting it to module scope and passing currentStep/disableAll/rtl as props would avoid the churn.
|
|
||
| // Runtime Button is always TourNavigation's NavButton (which accepts | ||
| // `disabled`); reactour's narrower type omits it. | ||
| const Button = props.Button as FC<PropsWithChildren<NavButtonProps>>; |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
as casts — informational, no change needed (typescript-standards)
props.Button as FC<...> (line 35) and step as ChecklistStep (line 105) are as casts, which typescript-standards generally discourages. Both work around genuine limitations in reactour's narrower third-party types, and the Button cast carries a clear WHY comment. Flagging only for completeness — these are acceptable as-is.

Description
Replaces reactour's default Prev/Next with a custom tour navigation and the interactive-step UX layer that sits on top of it: progress dots, a completion-gated "Next", an action checklist in the popover, and auto-progress. Visible behavior becomes observable once the registry has tours (#2306 onwards).
What's in it
Custom navigation
current/visited/unvisited). Non-clickable — bouncing between arbitrary steps would leave reactour inconsistent on tours with interaction gates.setSteps.Step completion + gated "Next"
TourProgressContexttracks per-step completion (aSetof completed step indices), reset whenever a tour opens.Action checklist
TourStepChecklistrenders the step's required action in the popover and ticks it off —Circle→CircleCheckwith a check animation and strike-through — when complete.tourActionLabeland may embed bold targets (e.g. "Select the Greet task"), rendered viarenderInline(exported fromTourContent). Each interaction's label is authored in the PR that introduces that interaction (feat: Guided Tours Framework (Navigating the Editor) #2299 / feat: Guided Tours Framework (First Pipeline) #2347 / feat: Guided Tours Framework (Subgraphs) #2365).Auto-progress
TourAutoAdvanceadvances ~800ms after a fresh completion — long enough to see the tick register. Steps already complete on arrival (e.g. revisited via Back) stay on a manual click; clicking "Next" advances immediately and cancels the pending auto-advance.File layout
TourNavigation.tsx(TourNavigation,NavButton,GatedNextButton/renderNextButton,TourStepChecklist,TourAutoAdvance, and the module-level visit-tracking helpers).TourPopover.tsxkeeps popover styling/positioning, the viewport clamp bridge, and the completion actions.TourProgressContext.tsxholds the completion state +useTourProgresshook. Tests:TourNavigation.test.tsxandTourProgressContext.test.tsx.Popover polish
Related Issue and Pull requests
Progresses https://github.com/Shopify/oasis-frontend/issues/583
Depends on #2348 (foundation). The interaction detection that drives completion lands in #2299; interaction-specific checklist labels are authored in #2299 / #2347 / #2365.
Type of Change
Checklist
Test Instructions
With a tour registered (use this branch with #2306 on top):
Dots
Checklist + gated Next
Visit-aware Next + revisits
Reset between tours
/learn/tours, start a different one. Completion + visited-max reset for the new tour.