From f5de6e1bca8c0313657ea36e6418f3351b3b2b2a Mon Sep 17 00:00:00 2001 From: Abhishek Date: Mon, 13 Jul 2026 19:24:30 +0530 Subject: [PATCH] chore: tooltip design enhancements (#532) --- ui/package.json | 1 + ui/src/app/globals.css | 39 +++++ .../components/WorkflowTesterPanel.tsx | 14 +- .../[workflowId]/run/[runId]/page.tsx | 10 +- .../onboarding/OnboardingTooltip.tsx | 137 +++++++++++------- 5 files changed, 130 insertions(+), 71 deletions(-) diff --git a/ui/package.json b/ui/package.json index 051b5664..0b365dd2 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,6 +16,7 @@ "dependencies": { "@calcom/embed-react": "^1.5.3", "@dagrejs/dagre": "^1.1.4", + "@floating-ui/react-dom": "^2.1.9", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.12", diff --git a/ui/src/app/globals.css b/ui/src/app/globals.css index cc929a23..76092673 100644 --- a/ui/src/app/globals.css +++ b/ui/src/app/globals.css @@ -150,6 +150,45 @@ animation: spin-slow 3s linear infinite; } +/* Attention pulse applied by OnboardingTooltip to the element its arrow points + at; removed automatically when the tooltip is dismissed. Blue matches the + tooltip body so the ring and the callout read as one unit. Two shadow layers: + a constant outline so the target stays highlighted between pulses, plus an + expanding wave, with a slight breathing scale on top. */ +.onboarding-pulse { + animation: onboarding-pulse 1.5s ease-out infinite; +} + +@keyframes onboarding-pulse { + 0% { + box-shadow: + 0 0 0 3px rgba(59, 130, 246, 0.75), + 0 0 0 3px rgba(59, 130, 246, 0.6); + transform: scale(1); + } + 40% { + transform: scale(1.04); + } + 70% { + box-shadow: + 0 0 0 3px rgba(59, 130, 246, 0.75), + 0 0 0 18px rgba(59, 130, 246, 0); + transform: scale(1); + } + 100% { + box-shadow: + 0 0 0 3px rgba(59, 130, 246, 0.75), + 0 0 0 18px rgba(59, 130, 246, 0); + } +} + +@media (prefers-reduced-motion: reduce) { + .onboarding-pulse { + animation: none; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.6); + } +} + @layer components { /* CSS-only audio-waveform motif for the auth brand panel. A row of bars that breathe at staggered intervals, evoking live voice. Decorative only. */ diff --git a/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx b/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx index 61648dd3..a30ee953 100644 --- a/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx +++ b/ui/src/app/workflow/[workflowId]/components/WorkflowTesterPanel.tsx @@ -47,7 +47,7 @@ export function WorkflowTesterPanel({ onRuntimeNodeTransition, }: WorkflowTesterPanelProps) { const auth = useAuth(); - const { hasSeenTooltip, markTooltipSeen, markActionCompleted } = useOnboarding(); + const { markActionCompleted } = useOnboarding(); const { isAuthenticated, loading: authLoading, getAccessToken } = auth; const [accessToken, setAccessToken] = useState(null); const [activeMode, setActiveMode] = useState<"audio" | "text">("audio"); @@ -113,7 +113,6 @@ export function WorkflowTesterPanel({ } markActionCompleted("web_call_started"); - markTooltipSeen("web_call"); posthog.capture(PostHogEvent.WEB_CALL_INITIATED, { workflow_id: workflowId, workflow_run_id: response.data.id, @@ -126,22 +125,21 @@ export function WorkflowTesterPanel({ } finally { setCreatingVoiceRun(false); } - }, [accessToken, disabled, markActionCompleted, markTooltipSeen, workflowId]); + }, [accessToken, disabled, markActionCompleted, workflowId]); const authUnavailableReason = tokenReady && !accessToken ? "Authentication is required before testing can start." : null; const effectiveDisabledReason = disabledReason ?? authUnavailableReason; const testerBlocked = disabled || authUnavailableReason !== null; - const showRunTestTooltip = + const runTestTooltipEnabled = showWebCallOnboarding && isVisible && activeMode === "audio" && !voiceRunId && tokenReady && !!accessToken && - !testerBlocked && - !hasSeenTooltip("web_call"); + !testerBlocked; return (
@@ -265,12 +263,12 @@ export function WorkflowTesterPanel({ markTooltipSeen("web_call")} showNext={false} - isVisible={showRunTestTooltip} + enabled={runTestTooltipEnabled} />
); diff --git a/ui/src/app/workflow/[workflowId]/run/[runId]/page.tsx b/ui/src/app/workflow/[workflowId]/run/[runId]/page.tsx index e5ce4596..27f095f2 100644 --- a/ui/src/app/workflow/[workflowId]/run/[runId]/page.tsx +++ b/ui/src/app/workflow/[workflowId]/run/[runId]/page.tsx @@ -31,7 +31,6 @@ import { Skeleton } from '@/components/ui/skeleton'; import { ConversationRailFrame, RealtimeFeedback, WorkflowRunLogs } from '@/components/workflow/conversation'; import { PostHogEvent } from '@/constants/posthog-events'; import { WORKFLOW_RUN_MODES } from '@/constants/workflowRunModes'; -import { useOnboarding } from '@/context/OnboardingContext'; import { useAuth } from '@/lib/auth'; import { downloadFile, getSignedUrl } from '@/lib/files'; import { cn } from '@/lib/utils'; @@ -602,7 +601,6 @@ export default function WorkflowRunPage() { const auth = useAuth(); const [workflowRun, setWorkflowRun] = useState(null); const [workflowName, setWorkflowName] = useState(null); - const { hasSeenTooltip, markTooltipSeen } = useOnboarding(); const customizeButtonRef = useRef(null); // Redirect if not authenticated @@ -747,11 +745,6 @@ export default function WorkflowRunPage() {