An open API service indexing awesome lists of open source software.

https://github.com/joaom00/selection-popover

Easy-to-use, unstyled, composable react selection popover.
https://github.com/joaom00/selection-popover

medium popover radix-ui react selection share text-selection unstyled

Last synced: 10 months ago
JSON representation

Easy-to-use, unstyled, composable react selection popover.

Awesome Lists containing this project

README

          






Easy-to-use, composable react selection popover

![npm version](https://img.shields.io/npm/v/selection-popover.svg)
![npm downloads](https://img.shields.io/npm/dm/selection-popover.svg)

## Install

```bash
npm install selection-popover
```

## Content

- [Anatomy](#anatomy)
- [API Reference](#api-reference)
- [Root](#root)
- [Trigger](#trigger)
- [Portal](#portal)
- [Content](#content)
- [Arrow](#arrow)
- [Examples](#examples)
- [Origin-aware animations](#origin-aware-animations)
- [Collision-aware animations](#collision-aware-animations)
- [Unmount animations](#unmount-animations)
- [Use with Radix Toolbar](#use-with-radix-toolbar)
- [Acknowledgements](#acknowledgements)

## Anatomy

Import all parts and piece them together.

```jsx
import * as Selection from 'selection-popover'

export default () => (








)
```

## API Reference

### Root

Contains all the parts of a selection.

| Prop | Type | Default | Description |
| -------------- | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `defaultOpen` | `boolean` | - | The open state of the hover card when it is initially rendered. Use when you do not need to control its open state. |
| `open` | `boolean` | - | The controlled open state of the popover. Must be used in conjunction with `onOpenChange`. |
| `onOpenChange` | `(open: boolean) => void` | - | Event handler called when the open state of the popover changes. |
| `whileSelect` | `boolean` | `false` | When `true`, the popover will open while the text is selected, otherwise only when the mouse up. |
| `disabled` | `boolean` | `false` | When true, the popover won't open when text is selected. |
| `openDelay` | `number` | `0` | The duration from when release the mouse until the content opens. In `whileSelect` is when you start the selection. |
| `closeDelay` | `number` | `0` | The duration from when you click outside of the content until the content closes. |

### Trigger

The area that opens the popover. Wrap it around the target you want the popover to open when a text is selected.

| Prop | Type | Default | Description |
| --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `asChild` | `boolean` | `false` | Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node. |

### Portal

When used, portals the content part into the `body`.

| Prop | Type | Default | Description |
| ------------ | ------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `forceMount` | `boolean` | - | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. If used on this part, it will be inherited by `Selection.Content`. |
| `container` | `HTMLElement` | `document.body` | Specify a container element to portal the content into. |

### Content

The component that pops out when a text is selected.

| Prop | Type | Default | Description |
| ---------------------- | --------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `asChild` | `boolean` | false | Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node. |
| `forceMount` | `boolean` | - | Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries. It inherits from `Selection.Portal`. |
| `side` | `"top" \| "right" \| "bottom" \| "left"` | `top` | The preferred side of the selection to render against when open. Will be reversed when collisions occur and `avoidCollisions` is enabled. |
| `sideOffset` | `number` | `0` | The distance in pixels from the selection. |
| `align` | `"start" \| "center" \| "end"` | `center` | The preferred alignment against the selection. May change when collisions occur. |
| `alignOffset` | `number` | `0` | An offset in pixels from the `"start"` or `"end"` alignment options. |
| `avoidCollisions` | `boolean` | `true` | When `true`, overrides the `side` and `align` preferences to prevent collisions with boundary edges. |
| `collisionBoundary` | `Element \| null \| Array` | `[]` | The element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. |
| `collisionPadding` | `number \| Partial>` | `0` | The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: `{ top: 20, left: 20 }`. |
| `arrowPadding` | `number` | `0` | The padding between the arrow and the edges of the content. If your content has `border-radius`, this will prevent it from overflowing the corners. |
| `sticky` | `"partial" \| "always"` | `partial` | The sticky behavior on the align axis. `"partial"` will keep the content in the boundary as long as the trigger is at least partially in the boundary whilst `"always"` will keep the content in the boundary regardless. |
| `hideWhenDetached` | `boolean` | `false` | Whether to hide the content when the text becomes fully occluded. |
| `onEscapeKeyDown` | `(event: KeyboardEvent) => void` | - | Event handler called when the escape key is down. It can be prevented by calling `event.preventDefault`. |
| `onPointerDownOutside` | `(event: PointerDownOutsideEvent) => void` | - | Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| `onFocusOutside` | `(event: FocusOutsideEvent) => void` | - | Event handler called when focus moves outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |
| `onInteractOutside` | `(event: PointerDownOutsideEvent \| FocusOutsideEvent) => void` | - | Event handler called when an interaction (pointer or focus event) happens outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |

| Data Attribute | Values |
| -------------- | ---------------------------------------- |
| `[data-state]` | `"open" \| "closed"` |
| `[data-side]` | `"left" \| "right" \| "bottom" \| "top"` |
| `[data-align]` | `"start" \| "end" \| "center"` |

| CSS Variable | Description |
| ---------------------------------------------- | ----------------------------------------------------------------------------- |
| `--selection-popover-content-transform-origin` | The `transform-origin` computed from the content and arrow positions/offsets. |
| `--selection-popover-select-width` | The width of the select. |
| `--selection-popover-select-height` | The height of the select. |

### Arrow

An optional arrow element to render alongside the popover. This can be used to help visually link the selected text with the `Selection.Content`. Must be rendered inside `Selection.Content`.

| Prop | Type | Default | Description |
| --------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `asChild` | `boolean` | `false` | Change the component to the HTML tag or custom component of the only child. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node. |
| `width` | `number` | `10` | The width of the arrow in pixels. |
| `height` | `number` | `5` | The height of the arrow in pixels. |

## Examples

### Origin-aware animations

```jsx
// index.jsx
import * as Selection from 'selection-popover'
import './styles.css'

export default () => (

...

...


)
```

```css
/* styles.css */
.SelectionContent {
transform-origin: var(--selection-popover-content-transform-origin);
animation: scaleIn 500ms cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
```

### Collision-aware animations

```jsx
// index.jsx
import * as Selection from 'selection-popover'
import './styles.css'

export default () => (

...

...


)
```

```css
/* styles.css */
.SelectionContent {
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.SelectionContent[data-state='open'][data-side='top'] {
animation-name: slideDownAndFade;
}
.SelectionContent[data-state='open'][data-side='bottom'] {
animation-name: slideUpAndFade;
}

@keyframes slideDownAndFade {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes slideUpAndFade {
from {
opacity: 0;
transform: translateY(2px));
}
to {
opacity: 1;
transform: translateY(0);
}
}
```

### Unmount animations

```jsx
// index.jsx
import * as Selection from 'selection-popover'
import './styles.css'

export default () => (

...

...


)
```

```css
/* styles.css */
.SelectionContent {
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.SelectionContent[data-state='open'] {
animation-name: slideDownAndFade;
}
.SelectionContent[data-state='closed'] {
animation-name: slideUpAndFade;
}

@keyframes slideDownAndFade {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes slideUpAndFade {
from {
opacity: 1;
transform: translateY(0));
}
to {
opacity: 0;
transform: translateY(-2px);
}
}
```

### Use with [Radix Toolbar](https://www.radix-ui.com/docs/primitives/components/toolbar)

```jsx
import * as Selection from 'selection-popover'
import * as Toolbar from '@radix-ui/react-toolbar'

export default () => (

...


...




)
```

## Acknowledgements

- API heavily inspired on [Radix UI](https://www.radix-ui.com/)
- Inspired by this [tweet](https://twitter.com/TobiasWhetton/status/1612821266242715648) from [Tobias Whetton](https://twitter.com/TobiasWhetton)