mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-28 10:26:33 +02:00
refactor: enhance onboarding tour UI and functionality
- Updated tooltip and spotlight styles for improved visibility and animation. - Adjusted background and text colors based on dark mode settings for better user experience. - Introduced animation for tooltip content changes to enhance user engagement. - Refactored rendering logic to ensure spotlight updates sync with tooltip animations.
This commit is contained in:
parent
e46a0e0a95
commit
0621304fbd
1 changed files with 113 additions and 38 deletions
|
|
@ -15,8 +15,7 @@ const TOUR_STEPS: TourStep[] = [
|
||||||
{
|
{
|
||||||
target: '[data-joyride="connector-icon"]',
|
target: '[data-joyride="connector-icon"]',
|
||||||
title: "Connect your data sources",
|
title: "Connect your data sources",
|
||||||
content:
|
content: "Connect and sync data from Gmail, Drive, Slack, Notion, Jira, Confluence, and more.",
|
||||||
"Connect and sync data from Gmail, Drive, Slack, Notion, Jira, Confluence, and more.",
|
|
||||||
placement: "bottom",
|
placement: "bottom",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +85,7 @@ function Spotlight({
|
||||||
}) {
|
}) {
|
||||||
const rect = targetEl.getBoundingClientRect();
|
const rect = targetEl.getBoundingClientRect();
|
||||||
const padding = 6;
|
const padding = 6;
|
||||||
const shadowColor = isDarkMode ? "#172554" : "#0c1a3a";
|
const shadowColor = isDarkMode ? "#172554" : "#3b82f6";
|
||||||
|
|
||||||
// Check if this is the connector icon step - verify both the selector matches AND the element matches
|
// Check if this is the connector icon step - verify both the selector matches AND the element matches
|
||||||
// This prevents the shape from changing before targetEl updates
|
// This prevents the shape from changing before targetEl updates
|
||||||
|
|
@ -110,7 +109,9 @@ function Spotlight({
|
||||||
width: isConnectorStep ? circleSize + padding * 2 : rect.width + padding * 2,
|
width: isConnectorStep ? circleSize + padding * 2 : rect.width + padding * 2,
|
||||||
height: isConnectorStep ? circleSize + padding * 2 : rect.height + padding * 2,
|
height: isConnectorStep ? circleSize + padding * 2 : rect.height + padding * 2,
|
||||||
borderRadius: isConnectorStep ? "50%" : 8,
|
borderRadius: isConnectorStep ? "50%" : 8,
|
||||||
boxShadow: `0 0 0 9999px rgba(0, 0, 0, 0.6)`,
|
boxShadow: isDarkMode
|
||||||
|
? `0 0 0 9999px rgba(0, 0, 0, 0.6)`
|
||||||
|
: `0 0 0 9999px rgba(0, 0, 0, 0.3)`,
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
zIndex: 99996,
|
zIndex: 99996,
|
||||||
}}
|
}}
|
||||||
|
|
@ -124,7 +125,9 @@ function Spotlight({
|
||||||
width: isConnectorStep ? circleSize : rect.width,
|
width: isConnectorStep ? circleSize : rect.width,
|
||||||
height: isConnectorStep ? circleSize : rect.height,
|
height: isConnectorStep ? circleSize : rect.height,
|
||||||
borderRadius: isConnectorStep ? "50%" : 8,
|
borderRadius: isConnectorStep ? "50%" : 8,
|
||||||
boxShadow: `0 0 10px 2px ${shadowColor}CC, 0 0 20px 6px ${shadowColor}99, 0 0 40px 12px ${shadowColor}66`,
|
boxShadow: isDarkMode
|
||||||
|
? `0 0 10px 2px ${shadowColor}CC, 0 0 20px 6px ${shadowColor}99, 0 0 40px 12px ${shadowColor}66`
|
||||||
|
: `0 0 6px 1px ${shadowColor}80, 0 0 12px 3px ${shadowColor}50, 0 0 20px 6px ${shadowColor}30`,
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
zIndex: 99997,
|
zIndex: 99997,
|
||||||
}}
|
}}
|
||||||
|
|
@ -153,12 +156,25 @@ function TourTooltip({
|
||||||
onSkip: () => void;
|
onSkip: () => void;
|
||||||
isDarkMode: boolean;
|
isDarkMode: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const [contentKey, setContentKey] = useState(stepIndex);
|
||||||
|
const [shouldAnimate, setShouldAnimate] = useState(false);
|
||||||
|
const prevStepIndexRef = useRef(stepIndex);
|
||||||
const isLastStep = stepIndex === totalSteps - 1;
|
const isLastStep = stepIndex === totalSteps - 1;
|
||||||
const isFirstStep = stepIndex === 0;
|
const isFirstStep = stepIndex === 0;
|
||||||
|
|
||||||
const bgColor = isDarkMode ? "#18181b" : "#18181b"; // Dark tooltip for both modes as shown in image
|
// Update content key when step changes to trigger animation
|
||||||
const textColor = "#ffffff";
|
// Only animate if stepIndex actually changes (not on initial mount)
|
||||||
const mutedTextColor = "#a1a1aa";
|
useEffect(() => {
|
||||||
|
if (prevStepIndexRef.current !== stepIndex) {
|
||||||
|
setShouldAnimate(true);
|
||||||
|
setContentKey(stepIndex);
|
||||||
|
prevStepIndexRef.current = stepIndex;
|
||||||
|
}
|
||||||
|
}, [stepIndex]);
|
||||||
|
|
||||||
|
const bgColor = isDarkMode ? "#18181b" : "#ffffff";
|
||||||
|
const textColor = isDarkMode ? "#ffffff" : "#18181b";
|
||||||
|
const mutedTextColor = isDarkMode ? "#a1a1aa" : "#71717a";
|
||||||
|
|
||||||
// Calculate pointer line position
|
// Calculate pointer line position
|
||||||
const getPointerStyles = (): React.CSSProperties => {
|
const getPointerStyles = (): React.CSSProperties => {
|
||||||
|
|
@ -192,7 +208,7 @@ function TourTooltip({
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPointer = () => {
|
const renderPointer = () => {
|
||||||
const lineColor = "#18181B";
|
const lineColor = isDarkMode ? "#18181B" : "#ffffff";
|
||||||
|
|
||||||
if (position.pointerPosition === "left") {
|
if (position.pointerPosition === "left") {
|
||||||
return (
|
return (
|
||||||
|
|
@ -250,7 +266,14 @@ function TourTooltip({
|
||||||
width: 6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
backgroundColor: i === stepIndex ? "#ffffff" : "#52525b",
|
backgroundColor:
|
||||||
|
i === stepIndex
|
||||||
|
? isDarkMode
|
||||||
|
? "#ffffff"
|
||||||
|
: "#18181b"
|
||||||
|
: isDarkMode
|
||||||
|
? "#52525b"
|
||||||
|
: "#d4d4d8",
|
||||||
transition: "background-color 0.2s",
|
transition: "background-color 0.2s",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
@ -269,6 +292,7 @@ function TourTooltip({
|
||||||
top: position.top,
|
top: position.top,
|
||||||
left: position.left,
|
left: position.left,
|
||||||
width: 280,
|
width: 280,
|
||||||
|
transition: "top 0.4s cubic-bezier(0.4, 0, 0.2, 1), left 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onKeyDown={(e) => e.stopPropagation()}
|
onKeyDown={(e) => e.stopPropagation()}
|
||||||
|
|
@ -281,11 +305,19 @@ function TourTooltip({
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: bgColor,
|
backgroundColor: bgColor,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
|
boxShadow: isDarkMode
|
||||||
|
? "0 25px 50px -12px rgba(0, 0, 0, 0.5)"
|
||||||
|
: "0 25px 50px -12px rgba(0, 0, 0, 0.15)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div>
|
<div
|
||||||
|
key={contentKey}
|
||||||
|
style={{
|
||||||
|
animation: shouldAnimate ? "fadeInSlide 0.3s ease-out" : "none",
|
||||||
|
}}
|
||||||
|
onAnimationEnd={() => setShouldAnimate(false)}
|
||||||
|
>
|
||||||
<h3 id="tour-title" className="text-sm font-semibold mb-1.5" style={{ color: textColor }}>
|
<h3 id="tour-title" className="text-sm font-semibold mb-1.5" style={{ color: textColor }}>
|
||||||
{step.title}
|
{step.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -349,6 +381,8 @@ export function OnboardingTour() {
|
||||||
const [isActive, setIsActive] = useState(false);
|
const [isActive, setIsActive] = useState(false);
|
||||||
const [stepIndex, setStepIndex] = useState(0);
|
const [stepIndex, setStepIndex] = useState(0);
|
||||||
const [targetEl, setTargetEl] = useState<Element | null>(null);
|
const [targetEl, setTargetEl] = useState<Element | null>(null);
|
||||||
|
const [spotlightTargetEl, setSpotlightTargetEl] = useState<Element | null>(null);
|
||||||
|
const [spotlightStepTarget, setSpotlightStepTarget] = useState<string | null>(null);
|
||||||
const [position, setPosition] = useState<TooltipPosition | null>(null);
|
const [position, setPosition] = useState<TooltipPosition | null>(null);
|
||||||
const [targetRect, setTargetRect] = useState<DOMRect | null>(null);
|
const [targetRect, setTargetRect] = useState<DOMRect | null>(null);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
@ -395,6 +429,8 @@ export function OnboardingTour() {
|
||||||
if (el) {
|
if (el) {
|
||||||
setIsActive(true);
|
setIsActive(true);
|
||||||
setTargetEl(el);
|
setTargetEl(el);
|
||||||
|
setSpotlightTargetEl(el);
|
||||||
|
setSpotlightStepTarget(TOUR_STEPS[0].target);
|
||||||
setTargetRect(el.getBoundingClientRect());
|
setTargetRect(el.getBoundingClientRect());
|
||||||
setPosition(calculatePosition(el, TOUR_STEPS[0].placement));
|
setPosition(calculatePosition(el, TOUR_STEPS[0].placement));
|
||||||
}
|
}
|
||||||
|
|
@ -449,6 +485,17 @@ export function OnboardingTour() {
|
||||||
}
|
}
|
||||||
}, [isActive, updateTarget, currentStep]);
|
}, [isActive, updateTarget, currentStep]);
|
||||||
|
|
||||||
|
// Delay spotlight update to sync with tooltip animation
|
||||||
|
useEffect(() => {
|
||||||
|
if (targetEl && currentStep) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setSpotlightTargetEl(targetEl);
|
||||||
|
setSpotlightStepTarget(currentStep.target);
|
||||||
|
}, 100);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [targetEl, currentStep]);
|
||||||
|
|
||||||
// Ensure target element is above overlay layers so content is fully visible
|
// Ensure target element is above overlay layers so content is fully visible
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!targetEl || !isActive) return;
|
if (!targetEl || !isActive) return;
|
||||||
|
|
@ -514,32 +561,60 @@ export function OnboardingTour() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div className="fixed inset-0 z-[99995]">
|
<>
|
||||||
{/* Clickable backdrop to close */}
|
<style>{`
|
||||||
<button
|
@keyframes fadeInSlide {
|
||||||
type="button"
|
from {
|
||||||
className="fixed inset-0 w-full h-full bg-transparent border-0 cursor-default"
|
opacity: 0;
|
||||||
onClick={handleOverlayClick}
|
transform: translateY(8px);
|
||||||
aria-label="Close tour"
|
}
|
||||||
/>
|
to {
|
||||||
{/* Only render Spotlight and TourTooltip when we have target data */}
|
opacity: 1;
|
||||||
{targetEl && position && currentStep && targetRect && (
|
transform: translateY(0);
|
||||||
<>
|
}
|
||||||
<Spotlight targetEl={targetEl} isDarkMode={isDarkMode} currentStepTarget={currentStep.target} />
|
}
|
||||||
<TourTooltip
|
@keyframes fadeIn {
|
||||||
step={currentStep}
|
from {
|
||||||
stepIndex={stepIndex}
|
opacity: 0;
|
||||||
totalSteps={TOUR_STEPS.length}
|
}
|
||||||
position={position}
|
to {
|
||||||
targetRect={targetRect}
|
opacity: 1;
|
||||||
onNext={handleNext}
|
}
|
||||||
onPrev={handlePrev}
|
}
|
||||||
onSkip={handleSkip}
|
`}</style>
|
||||||
isDarkMode={isDarkMode}
|
<div className="fixed inset-0 z-[99995]">
|
||||||
/>
|
{/* Clickable backdrop to close */}
|
||||||
</>
|
<button
|
||||||
)}
|
type="button"
|
||||||
</div>,
|
className="fixed inset-0 w-full h-full bg-transparent border-0 cursor-default"
|
||||||
|
onClick={handleOverlayClick}
|
||||||
|
aria-label="Close tour"
|
||||||
|
/>
|
||||||
|
{/* Only render Spotlight and TourTooltip when we have target data */}
|
||||||
|
{targetEl && position && currentStep && targetRect && (
|
||||||
|
<>
|
||||||
|
{spotlightTargetEl && spotlightStepTarget && (
|
||||||
|
<Spotlight
|
||||||
|
targetEl={spotlightTargetEl}
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
|
currentStepTarget={spotlightStepTarget}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TourTooltip
|
||||||
|
step={currentStep}
|
||||||
|
stepIndex={stepIndex}
|
||||||
|
totalSteps={TOUR_STEPS.length}
|
||||||
|
position={position}
|
||||||
|
targetRect={targetRect}
|
||||||
|
onNext={handleNext}
|
||||||
|
onPrev={handlePrev}
|
||||||
|
onSkip={handleSkip}
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>,
|
||||||
document.body
|
document.body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue