Example usages
Tooltip
Let's create a Tooltip component.
<Tooltip placement="top" label="Is tech making coffee better or worse?"> <button className="rounded-md bg-white px-8 py-4 text-sm font-semibold text-slate-900 shadow"> Tooltip Trigger </button></Tooltip>
1. Quick Way - using AttachedOverlay
component
import React from 'react';import type { Variant } from 'framer-motion';import clsx from 'clsx';
import { IAttachedOverlayProps, AttachedOverlay } from 'react-useoverlay';
// predefined color props for the tooltiptype color = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';interface ITooltipTheme { initial: string[]; animations: { open: Variant; close: Variant }; color: Record<color, string[]>;}
// I'm using Tailwind for theming of the tooltip.// But you are free to use any other solutions.const styles: ITooltipTheme = { initial: ['text-white font-medium rounded shadow-sm px-3 py-1.5'], animations: { open: { opacity: 1, transform: 'scale(1)', transition: { duration: 0.15, ease: [0.165, 0.84, 0.44, 1] }, }, close: { opacity: 0, transform: 'scale(0)', transition: { duration: 0.15, ease: [0.165, 0.84, 0.44, 1] }, }, }, color: { primary: ['bg-indigo-500'], secondary: ['bg-slate-500'], success: ['bg-green-500'], error: ['bg-red-500'], warning: ['bg-amber-500'], info: ['bg-sky-500'], },};
// component prop interface// we are omiting `overlay` & `children` props because we are gonna handle it by ourselfexport interface MyCustomTooltipProps extends Omit<IAttachedOverlayProps, 'overlay' | 'children'> { label: string; // you can also accept ReactNode etc className?: string; color?: color; children: JSX.Element;}
// actual componentexport const Tooltip: React.FC<MyCustomTooltipProps> = ({ // define your defaults for the tooltip placement = 'top', interactive = true, useMotion = true, offset = 8, shift = 8, delay = { open: 0, close: 0 }, use = { hover: true, focus: true }, usePortal = true, // your custom props color = 'primary', label, children, className, ...rest}) => { const classes = clsx(styles.initial, styles.color[color], className);
return ( <AttachedOverlay {...{ interactive, placement, useMotion, offset, shift, delay, use, usePortal, }} overlay={() => <div className={classes}>{label}</div>} {...rest} > {/* AttachedOverlay expects function as a child */} {/* we are gonna need it later for other components */} {() => children} </AttachedOverlay> );};
2. Custom Way - using useOverlay
hook
import React, { cloneElement } from 'react';import type { Placement } from '@floating-ui/react-dom-interactions';import type { Variant } from 'framer-motion';import clsx from 'clsx';
import { IOverlay, useOverlay } from 'react-useoverlay';
// predefined color props for the tooltiptype color = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';export interface ITooltipTheme { initial: string[]; animations: { open: Variant; close: Variant }; color: Record<color, string[]>;}
// I'm using Tailwind for theming of the tooltip.// But you are free to use any other solutions.export const styles: ITooltipTheme = { initial: [ 'text-white font-medium rounded shadow-sm px-3 py-1.5', ], animations: { open: { opacity: 1, transform: 'scale(1)', transition: { duration: 0.15, ease: [0.165, 0.84, 0.44, 1] }, }, close: { opacity: 0, transform: 'scale(0)', transition: { duration: 0.15, ease: [0.165, 0.84, 0.44, 1] }, }, }, color: { primary: ['bg-indigo-500'], secondary: ['bg-slate-500'], success: ['bg-green-500'], error: ['bg-red-500'], warning: ['bg-amber-500'], info: ['bg-sky-500'], },};
// component prop interfaceexport interface ITooltipProps extends Pick< IOverlay, | 'delay' | 'useMotion' | 'interactive' | 'offset' | 'shift' | 'usePortal' | 'className' > { label: string; placement?: Placement; color?: color; children: JSX.Element;}
// actual componentexport const Tooltip: React.FC<ITooltipProps> = ({ interactive = true, useMotion = true, placement = 'top', color = 'primary', offset = 8, shift = 8, delay = { open: 0, close: 0 }, usePortal = true, label, children, className,}) => { const classes = clsx(styles.initial, styles.color[color], className );
// we are passing almost all props to useOverlay hook const { triggerProps, overlay } = useOverlay({ // we are checking this to disable the tooltip triggers interactive, // we are wrapping with AnimatePresence to make it motion compatible useMotion, // start of floating-ui props placement, role: 'tooltip', use: { click: false, dismiss: true, focus: true, hover: true }, usePortal, offset, shift, delay, animations: styles.animations, // framer motion // here you can pass your floating element // this overlay function gives you the close function // so you can use it in your component // we will see it in action in the next examples live modal & drawer // for tooltip we don't need to close element manually // because we are only using useHover and useFocus triggers overlay: () => <div className={classes}>{label}</div>, });
// we need to wrap string children in a span to make them interactive return ( <> {typeof children === 'string' ? ( <span {...triggerProps}>{children}</span> ) : ( // if children is not a string, we can just clone it cloneElement(children, triggerProps) )} {/* this is the actual overlay */} {cloneElement(overlay)} </> );};
What is React.cloneElement?