Tooltip

Tooltip is a small pop-up box that appears when a user places the mouse pointer over an element.

Basic Tooltip

import { useState } from 'react'

interface TooltipProps {
  text: string
  children: React.ReactNode
}

const Tooltip: React.FC<TooltipProps> = ({ text, children }) => {
  const [isVisible, setIsVisible] = useState(false)

  return (
    <div className="relative inline-block">
      <div
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
      >
        {children}
      </div>
      {isVisible && (
        <div className="absolute z-10 px-3 py-2 text-sm font-medium text-white bg-gray-900 dark:bg-gray-800 rounded-lg shadow-sm bottom-full left-1/2 transform -translate-x-1/2 -translate-y-2">
          {text}
        </div>
      )}
    </div>
  )
}

export default function Component() {
  return (
    <div className="flex items-center justify-center h-32">
      <Tooltip text="This is a simple tooltip">
        <button className="px-4 py-2 text-white bg-blue-500 rounded hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700">
          Hover me
        </button>
      </Tooltip>
    </div>
  )
}

Arrow Tooltip

import React, { useState, useRef, useEffect } from 'react'

interface TooltipProps {
  text: string
  position?: 'top' | 'right' | 'bottom' | 'left'
  children: React.ReactNode
}

const Tooltip: React.FC<TooltipProps> = ({ text, position = 'top', children }) => {
  const [isVisible, setIsVisible] = useState(false)
  const tooltipRef = useRef<HTMLDivElement>(null)
  const targetRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const updatePosition = () => {
      if (isVisible && tooltipRef.current && targetRef.current) {
        const targetRect = targetRef.current.getBoundingClientRect()
        const tooltipRect = tooltipRef.current.getBoundingClientRect()
        let top = 0
        let left = 0

        switch (position) {
          case 'top':
            top = -tooltipRect.height - 10
            left = (targetRect.width - tooltipRect.width) / 2
            break
          case 'right':
            top = (targetRect.height - tooltipRect.height) / 2
            left = targetRect.width + 10
            break
          case 'bottom':
            top = targetRect.height + 10
            left = (targetRect.width - tooltipRect.width) / 2
            break
          case 'left':
            top = (targetRect.height - tooltipRect.height) / 2
            left = -tooltipRect.width - 10
            break
        }

        tooltipRef.current.style.top = `${top}px`
        tooltipRef.current.style.left = `${left}px`
      }
    }

    updatePosition()
    window.addEventListener('resize', updatePosition)
    return () => window.removeEventListener('resize', updatePosition)
  }, [isVisible, position])

  return (
    <div className="relative inline-block">
      <div
        ref={targetRef}
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
      >
        {children}
      </div>
      {isVisible && (
        <div
          ref={tooltipRef}
          className={`absolute z-10 px-3 py-2 text-sm font-medium text-white bg-gray-900 dark:bg-gray-800 rounded-lg shadow-sm tooltip-${position}`}
          style={{ whiteSpace: 'nowrap' }}
        >
          {text}
          <div
            className={`absolute w-2 h-2 bg-gray-900 dark:bg-gray-800 transform rotate-45 ${
              position === 'top' ? 'bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2' :
              position === 'right' ? 'left-0 top-1/2 -translate-y-1/2 -translate-x-1/2' :
              position === 'bottom' ? 'top-0 left-1/2 -translate-x-1/2 -translate-y-1/2' :
              'right-0 top-1/2 -translate-y-1/2 translate-x-1/2'
            }`}
          ></div>
        </div>
      )}
    </div>
  )
}

export default function Component() {
  return (
    <div className="flex items-center justify-center space-x-4 h-64">
      <Tooltip text="Top tooltip" position="top">
        <button className="px-4 py-2 text-white bg-blue-500 rounded hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700">
          Top
        </button>
      </Tooltip>
      <Tooltip text="Right tooltip" position="right">
        <button className="px-4 py-2 text-white bg-green-500 rounded hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700">
          Right
        </button>
      </Tooltip>
      <Tooltip text="Bottom tooltip" position="bottom">
        <button className="px-4 py-2 text-white bg-red-500 rounded hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700">
          Bottom
        </button>
      </Tooltip>
      <Tooltip text="Left tooltip" position="left">
        <button className="px-4 py-2 text-white bg-purple-500 rounded hover:bg-purple-600 dark:bg-purple-600 dark:hover:bg-purple-700">
          Left
        </button>
      </Tooltip>
    </div>
  )
}

Animated Tooltip

import { useState } from 'react'

interface TooltipProps {
  text: string
  children: React.ReactNode
}

const Tooltip: React.FC<TooltipProps> = ({ text, children }) => {
  const [isVisible, setIsVisible] = useState(false)

  return (
    <div className="relative inline-block">
      <div
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
      >
        {children}
      </div>
      <div
        className={`
          absolute z-10 px-3 py-2 text-sm font-medium text-white bg-gray-900 dark:bg-gray-800 rounded-lg shadow-sm
          transition-all duration-300 ease-in-out
          ${isVisible ? 'opacity-100 visible' : 'opacity-0 invisible'}
          bottom-full left-1/2 transform -translate-x-1/2 -translate-y-2
        `}
      >
        {text}
      </div>
    </div>
  )
}

export default function Component() {
  return (
    <div className="flex items-center justify-center h-32">
      <Tooltip text="This tooltip fades in and out">
        <button className="px-4 py-2 text-white bg-indigo-500 rounded hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-700">
          Hover for animated tooltip
        </button>
      </Tooltip>
    </div>
  )
}

Multi Direction Tooltip

Adaptive tooltip that changes direction based on available space.

"use client"

import { useState, useRef, useEffect } from 'react'

type Direction = 'top' | 'right' | 'bottom' | 'left'

interface TooltipProps {
  text: string
  children: React.ReactNode
}

const Tooltip: React.FC<TooltipProps> = ({ text, children }) => {
  const [isVisible, setIsVisible] = useState(false)
  const [direction, setDirection] = useState<Direction>('top')
  const tooltipRef = useRef<HTMLDivElement>(null)
  const targetRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const updatePosition = () => {
      if (isVisible && tooltipRef.current && targetRef.current) {
        const targetRect = targetRef.current.getBoundingClientRect()
        const tooltipRect = tooltipRef.current.getBoundingClientRect()
        const viewportWidth = window.innerWidth
        const viewportHeight = window.innerHeight

        let newDirection: Direction = 'top'

        if (targetRect.top > tooltipRect.height) {
          newDirection = 'top'
        } else if (viewportWidth - targetRect.right > tooltipRect.width) {
          newDirection = 'right'
        } else if (viewportHeight - targetRect.bottom > tooltipRect.height) {
          newDirection = 'bottom'
        } else if (targetRect.left > tooltipRect.width) {
          newDirection = 'left'
        }

        setDirection(newDirection)
      }
    }

    updatePosition()
    window.addEventListener('resize', updatePosition)
    return () => window.removeEventListener('resize', updatePosition)
  }, [isVisible])

  const getTooltipStyles = () => {
    switch (direction) {
      case 'top':
        return 'bottom-full left-1/2 transform -translate-x-1/2 -translate-y-2'
      case 'right':
        return 'top-1/2 left-full transform translate-x-2 -translate-y-1/2'
      case 'bottom':
        return 'top-full left-1/2 transform -translate-x-1/2 translate-y-2'
      case 'left':
        return 'top-1/2 right-full transform -translate-x-2 -translate-y-1/2'
    }
  }

  return (
    <div className="relative inline-block">
      <div
        ref={targetRef}
        onMouseEnter={() => setIsVisible(true)}
        onMouseLeave={() => setIsVisible(false)}
      >
        {children}
      </div>
      {isVisible && (
        <div
          ref={tooltipRef}
          className={`
            absolute z-10 px-3 py-2 text-sm font-medium text-white bg-gray-900 dark:bg-gray-800 rounded-lg shadow-sm
            ${getTooltipStyles()}
          `}
        >
          {text}
          <div
            className={`absolute w-3 h-3 bg-gray-900 dark:bg-gray-800 transform rotate-45 ${
              direction === 'top' ? 'bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2' :
              direction === 'right' ? 'left-0 top-1/2 -translate-y-1/2 -translate-x-1/2' :
              direction === 'bottom' ? 'top-0 left-1/2 -translate-x-1/2 -translate-y-1/2' :
              'right-0 top-1/2 -translate-y-1/2 translate-x-1/2'
            }`}
          ></div>
        </div>
      )}
    </div>
  )
}

export default function Component() {
  return (
    <div className="flex items-center justify-center h-64">
      <Tooltip text="This tooltip adapts to available space">
        <button className="px-4 py-2 text-white bg-teal-500 rounded hover:bg-teal-600">
          Hover for multi-direction tooltip
        </button>
      </Tooltip>
    </div>
  )
}