MCPcopy Index your code
hub / github.com/alexkatz/react-tiny-popover

github.com/alexkatz/react-tiny-popover @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
17 symbols 42 edges 11 files 0 documented · 0% 6 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

react-tiny-popover

demo gif

Click here for a full demo :+1:

A lightweight, highly customizable, non-intrusive, and Typescript friendly popover react HOC with no other dependencies!

The component renders its child directly, without wrapping it with anything on the DOM, and in addition renders solely the JSX you provide when shown. It simply grabs the child component's coordinates and provides a robust and non-intrusive way for you to position your own content around the child. Your content will be appended to document.body (or an element of your choice) when shown, and removed when hidden. You can use it to generate little popups around input or button elements, menu fly-outs, or in pretty much any situation where you want some content to appear and disappear dynamically around a target. You can also specify your own location for your popover content or hook into the existing positioning process, allowing you to essentially make modal windows and the like, as well!

react-tiny-popover can also guard against container boundaries and reposition itself to prevent any kind of hidden overflow. You can specify a priority of desired positions to fall back to, if you'd like.

Optionally, you can provide a renderer function for your popover content that injects the popover's current position, in case your content needs to know where it sits in relation to its target.

Since react-tiny-popover tries to be as non-invasive as possible, it will simply render the content you provide with the position and padding from the target that you provide. If you'd like an arrow pointing to the target to appear along with your content and don't feel like building it yourself, you may be interested in wrapping your content with the customizable ArrowContainer component, also provided! ArrowContainer's arrow will follow its target dynamically, and handles boundary collisions as well.

Install

npm i react-tiny-popover --save

Examples

import { Popover } from 'react-tiny-popover'

...

<Popover
  isOpen={isPopoverOpen}
  positions={['top', 'bottom', 'left', 'right']} // preferred positions by priority
  content={

Hi! I'm popover content.

}
>


 setIsPopoverOpen(!isPopoverOpen)}>
    Click me!



</Popover>;
import { Popover } from 'react-tiny-popover'

...

<Popover
  isOpen={isPopoverOpen}
  positions={['top', 'left']} // if you'd like, you can limit the positions
  padding={10} // adjust padding here!
  reposition={false} // prevents automatic readjustment of content position that keeps your popover content within its parent's bounds
  onClickOutside={() => setIsPopoverOpen(false)} // handle click events outside of the popover/target here!
  content={({ position, nudgedLeft, nudgedTop }) => ( // you can also provide a render function that injects some useful stuff!





Hi! I'm popover content. Here's my current position: {position}.




I'm {` ${nudgedLeft} `} pixels beyond my boundary horizontally!




I'm {` ${nudgedTop} `} pixels beyond my boundary vertically!





  )}
>


 setIsPopoverOpen(!isPopoverOpen)}>Click me!


</Popover>;
import { useRef } from 'react';
import { Popover, ArrowContainer } from 'react-tiny-popover'

const clickMeButtonRef = useRef<HTMLButtonElement | undefined>();

<Popover
  isOpen={isPopoverOpen}
  positions={['top', 'right', 'left', 'bottom']}
  padding={10}
  onClickOutside={() => setIsPopoverOpen(false)}
  ref={clickMeButtonRef} // if you'd like a ref to your popover's child, you can grab one here
  content={({ position, childRect, popoverRect }) => (
    <ArrowContainer // if you'd like an arrow, you can import the ArrowContainer!
      position={position}
      childRect={childRect}
      popoverRect={popoverRect}
      arrowColor={'blue'}
      arrowSize={10}
      arrowStyle={{ opacity: 0.7 }}
      className='popover-arrow-container'
      arrowClassName='popover-arrow'
    >


 setIsPopoverOpen(!isPopoverOpen)}
      >
        Hi! I'm popover content. Here's my position: {position}.



    </ArrowContainer>
  )}
>
  <button onClick={() => setIsPopoverOpen(!isPopoverOpen)}>
    Click me!
  </button>
</Popover>;

If you'd like to use a custom React element as Popover's target, you'll have to pass the ref that Popover provides to an inner DOM element of your component. The best way to accomplish this is with React's ref forwarding API. Here's a simple example, using Typescript:

import React, { useState } from 'react';
import { Popover } from 'react-tiny-popover';

interface CustomComponentProps extends React.ComponentPropsWithoutRef<'div'> {
  onClick(): void;
}

const CustomComponent = React.forwardRef<HTMLDivElement, CustomComponentProps>((props, ref) => (



    {props.children}



));

const App: React.FC = () => {
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
  return (



      <Popover isOpen={isPopoverOpen} content={

hey from popover content

}>
        <CustomComponent onClick={() => setIsPopoverOpen(!isPopoverOpen)}>
          hey from a custom target component
        </CustomComponent>
      </Popover>



  );
};

export default App;

Hooks

If you prefer going completely headless (though react-tiny-popover is fairly headless as is), you may prefer usePopover and useArrowContainer instead.

To create your own custom arrow container, the useArrowContainer hook works as so:

import { useArrowContainer } from 'react-tiny-popover';

// ...

const { arrowContainerStyle, arrowStyle } = useArrowContainer({
  childRect // from PopoverState,
  popoverRect // from PopoverState,
  position // from PopoverState,
  arrowColor // string,
  arrowSize // number,
});

// ...

// You can then use these styles to render your arrow container in whatever way you'd like
return (






      {children}



);

Similarly, usePopover allows you to create your own popover component as so:

import { usePopover } from 'react-tiny-popover'

// ...

const onPositionPopover = useCallback(
  (popoverState: PopoverState) => setPopoverState(popoverState),
  [],
);

const [positionPopover, popoverRef] = usePopover({
  childRef,
  containerClassName,
  parentElement,
  transform,
  positions,
  align,
  padding,
  boundaryInset,
  boundaryElement,
  reposition,
  onPositionPopover,
});

// ...

After attaching popoverRef and childRef to the DOM, you can fire positionPopover at any time to update your popover's position.

This is a bit more advanced, but play around and see what you can come up with! Feel free to examine the internal Popover component to see how the hook is used there.

Small Breaking Change in 8.1

Prior to 8.1, the two DOM elements generated via React Portal by react-tiny-popover were given the ids react-tiny-popover-container and react-tiny-popover-scout. In 8.1 and above, both react-tiny-popover-container and react-tiny-popover-scout are now assigned as class names. This solves the issue of multiple DOM elements sharing the same id if you have more than one popover open at once.

If you select for react-tiny-popover-container or react-tiny-popover-scout by id in your code, you'll have to select via class name instead.

Migrating to 8.0

react-tiny-popover 8.0 removes the contentLocation prop and replaces it with a slightly more capable transform prop. By default, the transform prop behaves exactly as contentLocation did.

<Popover
  isOpen={isPopoverOpen}
  contentLocation={{ top: 20, left: 20 }} {/* no longer used */}
  content={

Hi! I'm popover content.

}
>
  {/* ... */}
</Popover>;

Becomes:

<Popover
  isOpen={isPopoverOpen}
  transform={{ top: 20, left: 20 }} { /* <-- you'll need to rename this prop, but that's all */}
  content={

Hi! I'm popover content.

}
>
  {/* ... */}
</Popover>;

Now, you have access to an additional handy prop, transformMode:

<Popover
  isOpen={isPopoverOpen}
  transform={{ top: 20, left: 20 }}
  transformMode='relative'{ /* <-- whoa cool */}
  content={

Hi! I'm popover content.

}
>
  {/* ... */}
</Popover>;

The above popover will now render 20 pixels down and left of where it originally would have appeared without the transform, rather than at a fixed/absolute position.

The other transformMode value, "absolute" is the default value when transformMode is omitted. This produces the same behavior that contentLocation did.

Migrating to 5.0

react-tiny-popover 5 and up has abandoned use of findDOMNode to gain a reference to Popover's target DOM node, and now explicitly relies on a ref. Since React has deprecated findDOMNode in StrictMode, now seems like an appropriate time to shift away from this under-the-hood logic toward a clearer and more declarative API.

If your code looked this way, it can stay this way. React elements handle refs out of the box with no issues:

<Popover
  isOpen={isPopoverOpen}
  content={

Hi! I'm popover content.

}
>


 setIsPopoverOpen(!isPopoverOpen)}>
    Click me!



</Popover>;

However, if you use a custom component as a your Popover's child, you'll have to implement ref forwarding. Without ref forwarding, Popover will not be able to inject a reference into your component and refer to it.

For example:

interface Props extends React.ComponentPropsWithoutRef<'div'> {
  onClick(): void;
}

// this component will no longer work as a Popover child
const CustomComponent: React.FC<Props> = props => (



    {props.children}



)

// instead, you'll simply implement ref forwarding, as so:
const CustomComponent = React.forwardRef<HTMLDivElement, Props>((props, ref) => (



    {props.children}



));

Check out React's ref forwarding API for more info, and see the examples above.

API

Popover

Property Type Required Description
children JSX.Element ✔️ The component you place here will render directly to the DOM. Totally headless. If you provide a custom component, it must use ref forwarding.
isOpen boolean ✔️ When this boolean is set to true, the popover is visible and tracks the target. When the boolean is false, the popover content is neither visible nor present on the DOM.
content JSX.Element or `(popoverState: PopoverState) =>

Extension points exported contracts — how you extend this code

GetNewPopoverRectProps (Interface)
(no doc)
src/util.ts

Core symbols most depended-on inside this repo

createRect
called by 4
src/util.ts
useElementRef
called by 2
src/useElementRef.ts
useArrowContainer
called by 1
src/useArrowContainer.ts
updatePopover
called by 1
src/Popover.tsx
renderChild
called by 1
src/Popover.tsx
renderPopover
called by 1
src/Popover.tsx
usePopover
called by 1
src/usePopover.ts
useMemoizedArray
called by 1
src/useMemoizedArray.ts

Shape

Function 16
Interface 1

Languages

TypeScript100%

Modules by API surface

src/util.ts7 symbols
src/Popover.tsx3 symbols
src/usePopover.ts1 symbols
src/useMemoizedArray.ts1 symbols
src/useHandlePrevValues.ts1 symbols
src/useElementRef.ts1 symbols
src/useArrowContainer.ts1 symbols
src/PopoverPortal.tsx1 symbols
src/ArrowContainer.tsx1 symbols

For agents

$ claude mcp add react-tiny-popover \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact