shadimage

Tooltip

Pop-Brutalist high-contrast tooltip with hard shadow.

components/ui/tooltip.tsx

import * as React from 'react'
import { motion, AnimatePresence } from 'motion/react'
import { cn } from '@/lib/utils'

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

export function Tooltip({
  content,
  children,
  position = 'top',
  className,
}: TooltipProps) {
  const [visible, setVisible] = React.useState(false)

  const positionStyles = {
    top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
    bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
    left: 'right-full top-1/2 -translate-y-1/2 mr-2',
    right: 'left-full top-1/2 -translate-y-1/2 ml-2',
  }

  const animVariants = {
    top: { initial: { opacity: 0, y: 5, scale: 0.9 }, animate: { opacity: 1, y: 0, scale: 1 } },
    bottom: { initial: { opacity: 0, y: -5, scale: 0.9 }, animate: { opacity: 1, y: 0, scale: 1 } },
    left: { initial: { opacity: 0, x: 5, scale: 0.9 }, animate: { opacity: 1, x: 0, scale: 1 } },
    right: { initial: { opacity: 0, x: -5, scale: 0.9 }, animate: { opacity: 1, x: 0, scale: 1 } },
  }

  return (
    <div
      className="relative inline-block"
      onMouseEnter={() => setVisible(true)}
      onMouseLeave={() => setVisible(false)}
      onFocus={() => setVisible(true)}
      onBlur={() => setVisible(false)}
    >
      {children}
      <AnimatePresence>
        {visible && (
          <motion.div
            initial={animVariants[position].initial}
            animate={animVariants[position].animate}
            exit={animVariants[position].initial}
            transition={{ type: 'spring', stiffness: 500, damping: 25 }}
            className={cn(
              'absolute z-50 whitespace-nowrap bg-rk-ink text-rk-primary text-xs font-mono font-bold px-3.5 py-1.5 rounded-xl rk-border-sm rk-shadow-md pointer-events-none',
              positionStyles[position],
              className
            )}
          >
            {content}
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  )
}
Visual style
brutalist
Interaction
tooltip

DependenciesBundle weight · light

clsxtailwind-merge