8.1 KiB

Tooltip Component

A flexible, accessible tooltip component that supports both regular positioning and special sidebar positioning logic with click-to-pin functionality. The tooltip is controlled by default, appearing on hover and pinning on click.

Features

  • 🎯 Smart Positioning: Automatically positions tooltips to stay within viewport bounds
  • 📱 Sidebar Support: Special positioning logic for sidebar/navigation elements
  • Accessible: Works with mouse interactions and click-to-pin functionality
  • 🎨 Customizable: Support for arrows, structured content, and custom JSX
  • 🌙 Theme Support: Built-in dark mode and theme variable support
  • Performance: Memoized calculations and efficient event handling
  • 📜 Scrollable: Content area scrolls when content exceeds max height
  • 📌 Click-to-Pin: Click to pin tooltips open, click outside or the close button to unpin
  • 🔗 Link Support: Full support for clickable links in descriptions, bullets, and body content
  • 🎮 Controlled by Default: Always uses controlled state management for consistent behavior
  • ⏱️ Hover Timing Controls: Optional long-hover requirement via delayAppearance and delay

Behavior

Default Behavior (Controlled)

  • Hover: Tooltips appear on hover with a small delay when leaving to prevent flickering
  • Click: Click the trigger to pin the tooltip open
  • Click tooltip: Pins the tooltip to keep it open
  • Click close button: Unpins and closes the tooltip (red X button in top-right when pinned)
  • Click outside: Unpins and closes the tooltip
  • Visual indicator: Pinned tooltips have a blue border and close button

Manual Control (Optional)

  • Use open and onOpenChange props for complete external control
  • Useful for complex state management or custom interaction patterns

Basic Usage

import { Tooltip } from '@/components/shared';

function MyComponent() {
  return (
    <Tooltip content="This is a helpful tooltip">
      <button>Hover me</button>
    </Tooltip>
  );
}

API Reference

Props

Prop Type Default Description
content ReactNode - Custom JSX content to display in the tooltip
tips TooltipTip[] - Structured content with title, description, bullets, and optional body
children ReactElement required Element that triggers the tooltip
sidebarTooltip boolean false Enables special sidebar positioning logic
position 'right' | 'left' | 'top' | 'bottom' 'right' Tooltip position (ignored if sidebarTooltip is true)
offset number 8 Distance in pixels between trigger and tooltip
maxWidth number | string 280 Maximum width constraint for the tooltip
open boolean undefined External open state (makes component fully controlled)
onOpenChange (open: boolean) => void undefined Callback for external control
arrow boolean false Shows a small triangular arrow pointing to the trigger element
portalTarget HTMLElement undefined DOM node to portal the tooltip into
header { title: string; logo?: ReactNode } - Optional header with title and logo
delay number 0 Optional hover-open delay (ms). If omitted or 0, opens immediately

TooltipTip Interface

interface TooltipTip {
  title?: string;        // Optional pill label
  description?: string;  // Optional description text (supports HTML including <a> tags)
  bullets?: string[];    // Optional bullet points (supports HTML including <a> tags)
  body?: React.ReactNode; // Optional custom JSX for this tip
}

Usage Examples

// Simple tooltip with hover and click-to-pin
<Tooltip content="This tooltip appears on hover and pins on click">
  <button>Hover me</button>
</Tooltip>

// Structured content with tips
<Tooltip 
  tips={[
    {
      title: "OCR Mode",
      description: "Choose how to process text in your documents.",
      bullets: [
        "<strong>Auto</strong> skips pages that already contain text.",
        "<strong>Force</strong> re-processes every page.",
        "<strong>Strict</strong> stops if text is found.",
        "<a href='https://docs.example.com' target='_blank'>Learn more</a>"
      ]
    }
  ]}
  header={{
    title: "Basic Settings Overview",
    logo: <img src="/logo.svg" alt="Logo" />
  }}
>
  <button>Settings</button>
</Tooltip>

Optional Hover Delay

// Show after a 1s hover
<Tooltip content="Appears after a long hover" delay={1000} />

// Custom long-hover duration (2 seconds)
<Tooltip content="Appears after 2s" delay={2000} />

Custom JSX Content

<Tooltip 
  content={
    <div>
      <h3>Custom Content</h3>
      <p>Any JSX you want here</p>
      <button>Action</button>
      <a href="https://example.com">External link</a>
    </div>
  }
>
  <button>Custom tooltip</button>
</Tooltip>

Mixed Content (Tips + Custom JSX)

<Tooltip 
  tips={[
    { title: "Section", description: "Description" }
  ]}
  content={<div>Additional custom content below tips</div>}
>
  <button>Mixed content</button>
</Tooltip>

Sidebar Tooltips

// For items in a sidebar/navigation
<Tooltip 
  content="This tooltip appears to the right of the sidebar"
  sidebarTooltip={true}
>
  <div className="sidebar-item">
    📁 File Manager
  </div>
</Tooltip>

With Arrows

<Tooltip 
  content="Tooltip with arrow pointing to trigger"
  arrow={true}
  position="top"
>
  <button>Arrow tooltip</button>
</Tooltip>

Manual Control (Advanced)

function ManualControlTooltip() {
  const [open, setOpen] = useState(false);
  
  return (
    <Tooltip 
      content="Fully controlled tooltip"
      open={open}
      onOpenChange={setOpen}
    >
      <button onClick={() => setOpen(!open)}>
        Toggle tooltip
      </button>
    </Tooltip>
  );
}

Click-to-Pin Interaction

How to Use (Default Behavior)

  1. Hover over the trigger element to show the tooltip
  2. Click the trigger element to pin the tooltip open
  3. Click the red X button in the top-right corner to close
  4. Click anywhere outside the tooltip to close
  5. Click the trigger again to toggle pin state

Visual States

  • Unpinned: Normal tooltip appearance
  • Pinned: Blue border, subtle glow, and close button (X) in top-right corner

The tooltip fully supports clickable links in all content areas:

  • Descriptions: Use <a href="..."> in description strings
  • Bullets: Use <a href="..."> in bullet point strings
  • Body: Use JSX <a> elements in the body ReactNode
  • Content: Use JSX <a> elements in custom content

Links automatically get proper styling with hover states and open in new tabs when using target="_blank".

Positioning Logic

Regular Tooltips

  • Uses the position prop to determine initial placement
  • Automatically clamps to viewport boundaries
  • Calculates optimal position based on trigger element's getBoundingClientRect()
  • Dynamic arrow positioning: Arrow stays aligned with trigger even when tooltip is clamped

Timing Details

  • Opening uses delay (ms) if provided; otherwise opens immediately. Closing occurs immediately when the cursor leaves (unless pinned).
  • All internal timers are cleared on state changes, mouse transitions, and unmount to avoid overlaps.
  • Only one tooltip can be open at a time; hovering a new trigger closes others immediately.

Sidebar Tooltips

  • When sidebarTooltip={true}, horizontal positioning is locked to the right of the sidebar
  • Vertical positioning follows the trigger but clamps to viewport
  • Smart sidebar detection: Uses getSidebarInfo() to determine which sidebar is active (tool panel vs quick access bar) and gets its exact position
  • Dynamic positioning: Adapts to whether the tool panel is expanded or collapsed
  • Conditional display: Only shows tooltips when the tool panel is active (sidebarInfo.isToolPanelActive)
  • No arrows - sidebar tooltips don't show arrows