improvement(tour): align product tour tooltip styling with emcn and fix spotlight overflow (#3854)

This commit is contained in:
Waleed
2026-03-30 16:59:53 -07:00
committed by GitHub
parent 27460f847c
commit 72eea64bf6
6 changed files with 49 additions and 38 deletions

View File

@@ -61,7 +61,7 @@ export const navTourSteps: Step[] = [
target: '[data-tour="nav-tasks"]',
title: 'Tasks',
content:
'Tasks that work for you. Mothership can create, edit, and delete resource throughout the platform. It can also perform actions on your behalf, like sending emails, creating tasks, and more.',
'Tasks that work for you. Mothership can create, edit, and delete resources throughout the platform. It can also perform actions on your behalf, like sending emails, creating tasks, and more.',
placement: 'right',
disableBeacon: true,
},

View File

@@ -1,6 +1,6 @@
'use client'
import { createContext, useCallback, useContext, useEffect, useState } from 'react'
import { createContext, useCallback, useContext } from 'react'
import type { TooltipRenderProps } from 'react-joyride'
import { TourTooltip } from '@/components/emcn'
@@ -59,18 +59,14 @@ export function TourTooltipAdapter({
closeProps,
}: TooltipRenderProps) {
const { isTooltipVisible, isEntrance, totalSteps } = useContext(TourStateContext)
const [targetEl, setTargetEl] = useState<HTMLElement | null>(null)
useEffect(() => {
const { target } = step
if (typeof target === 'string') {
setTargetEl(document.querySelector<HTMLElement>(target))
} else if (target instanceof HTMLElement) {
setTargetEl(target)
} else {
setTargetEl(null)
}
}, [step])
const { target } = step
const targetEl =
typeof target === 'string'
? document.querySelector<HTMLElement>(target)
: target instanceof HTMLElement
? target
: null
/**
* Forwards the Joyride tooltip ref safely, handling both

View File

@@ -114,6 +114,16 @@ export function useTour({
[steps.length, stopTour, cancelPendingTransitions, scheduleReveal]
)
useEffect(() => {
if (!run) return
const html = document.documentElement
const prev = html.style.scrollbarGutter
html.style.scrollbarGutter = 'stable'
return () => {
html.style.scrollbarGutter = prev
}
}, [run])
/** Stop the tour when disabled becomes true (e.g. navigating away from the relevant page) */
useEffect(() => {
if (disabled && run) {

View File

@@ -1329,8 +1329,11 @@ export const Sidebar = memo(function Sidebar() {
!hasOverflowTop && 'border-transparent'
)}
>
<div className='tasks-section flex flex-shrink-0 flex-col' data-tour='nav-tasks'>
<div className='flex h-[18px] flex-shrink-0 items-center justify-between px-4'>
<div
className='tasks-section mx-2 flex flex-shrink-0 flex-col'
data-tour='nav-tasks'
>
<div className='flex h-[18px] flex-shrink-0 items-center justify-between px-2'>
<div className='font-base text-[var(--text-icon)] text-small'>All tasks</div>
{!isCollapsed && (
<div className='flex items-center justify-center gap-2'>
@@ -1451,10 +1454,10 @@ export const Sidebar = memo(function Sidebar() {
</div>
<div
className='workflows-section relative mt-3.5 flex flex-col'
className='workflows-section relative mx-2 mt-3.5 flex flex-col'
data-tour='nav-workflows'
>
<div className='flex h-[18px] flex-shrink-0 items-center justify-between px-4'>
<div className='flex h-[18px] flex-shrink-0 items-center justify-between px-2'>
<div className='font-base text-[var(--text-icon)] text-small'>Workflows</div>
{!isCollapsed && (
<div className='flex items-center justify-center gap-2'>

View File

@@ -45,12 +45,12 @@ function TourCard({
return (
<>
<div className='flex items-center justify-between gap-2 px-4 pt-4 pb-2'>
<h3 className='min-w-0 font-medium text-[var(--text-primary)] text-sm leading-none'>
<h3 className='min-w-0 font-medium text-[var(--text-primary)] text-small leading-none'>
{title}
</h3>
<Button
variant='ghost'
className='h-[16px] w-[16px] flex-shrink-0 p-0'
className='relative h-[16px] w-[16px] flex-shrink-0 p-0 before:absolute before:inset-[-14px] before:content-[""]'
onClick={onClose}
aria-label='Close tour'
>
@@ -60,24 +60,23 @@ function TourCard({
</div>
<div className='px-4 pt-1 pb-3'>
<p className='text-[12px] text-[var(--text-secondary)] leading-[1.6]'>{description}</p>
<p className='text-[var(--text-secondary)] text-caption leading-[1.6]'>{description}</p>
</div>
<div className='flex items-center justify-between border-[var(--border)] border-t px-4 py-3'>
<span className='text-[11px] text-[var(--text-muted)] [font-variant-numeric:tabular-nums]'>
<div className='flex items-center justify-between rounded-b-xl border-[var(--border)] border-t bg-[color-mix(in_srgb,var(--surface-3)_50%,transparent)] px-4 py-2'>
<span className='text-[var(--text-muted)] text-xs [font-variant-numeric:tabular-nums]'>
{step} / {totalSteps}
</span>
<div className='flex items-center gap-1.5'>
<div className={cn(isFirst && 'invisible')}>
<Button
variant='default'
size='sm'
onClick={onBack}
tabIndex={isFirst ? -1 : undefined}
>
Back
</Button>
</div>
<Button
variant='default'
size='sm'
onClick={onBack}
tabIndex={isFirst ? -1 : undefined}
className={cn(isFirst && 'invisible')}
>
Back
</Button>
<Button variant='tertiary' size='sm' onClick={onNext}>
{isLast ? 'Done' : 'Next'}
</Button>
@@ -156,7 +155,7 @@ function TourTooltip({
const isCentered = placement === 'center'
const cardClasses = cn(
'w-[260px] overflow-hidden rounded-[8px] bg-[var(--bg)]',
'w-[260px] overflow-hidden rounded-xl bg-[var(--bg)]',
isEntrance && 'animate-tour-tooltip-in motion-reduce:animate-none',
className
)
@@ -181,7 +180,7 @@ function TourTooltip({
<div
className={cn(
cardClasses,
'pointer-events-auto relative border border-[var(--border)] shadow-sm'
'pointer-events-auto relative shadow-overlay ring-1 ring-foreground/10'
)}
>
{cardContent}
@@ -202,10 +201,7 @@ function TourTooltip({
sideOffset={10}
collisionPadding={12}
avoidCollisions
className='z-[10000300] outline-none'
style={{
filter: 'drop-shadow(0 0 0.5px var(--border)) drop-shadow(0 1px 2px rgba(0,0,0,0.1))',
}}
className='z-[10000300] outline-none drop-shadow-tour'
onOpenAutoFocus={(e) => e.preventDefault()}
onCloseAutoFocus={(e) => e.preventDefault()}
>

View File

@@ -126,6 +126,12 @@ export default {
'brand-inset': 'var(--shadow-brand-inset)',
card: 'var(--shadow-card)',
},
dropShadow: {
tour: [
'0 0 0.5px color-mix(in srgb, var(--text-primary) 10%, transparent)',
'0 4px 12px rgba(0,0,0,0.1)',
],
},
transitionProperty: {
width: 'width',
left: 'left',