Files
svelte-headlessui/src/routes/docs/1.0/popover/+page.svx
2023-06-14 18:36:11 -07:00

429 lines
16 KiB
Plaintext

# Popover
<script>
import Preview from "../_Preview.svelte";
</script>
<Preview url="examples/popover" code="" class="h-[520px] bg-orange-500"/>
## Basic example
Popovers are built using the `Popover`, `PopoverButton`, and `PopoverPanel` components.
Clicking the `PopoverButton` will automatically open/close the `PopoverPanel`. When the panel is open, clicking anywhere outside of its contents, pressing the Escape key, or tabbing away from it will close the `Popover`.
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
</script>
<Popover style="position: relative;">
<PopoverButton>Solutions</PopoverButton>
<PopoverPanel style="position: absolute; z-index: 10;">
<div class="panel-contents">
<a href="/analytics">Analytics</a>
<a href="/engagement">Engagement</a>
<a href="/security">Security</a>
<a href="/integrations">Integrations</a>
</div>
<img src="/solutions.jpg" alt="" />
</PopoverPanel>
</Popover>
<style>
.panel-contents {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
</style>
```
## Positioning the panel
To get your popover to actually render a floating panel near your button, you'll need to use some styling technique that relies on CSS, JS, or both. In the previous example, we used CSS absolute and relative positioning so that the panel renders near the button that opened it.
For more sophisticated approaches, you might use a library like Popper JS. We recommend using the [svelte-popperjs](https://github.com/bryanmylee/svelte-popperjs) wrapper and forwarding the actions to our components:
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
import { createPopperActions } from "svelte-popperjs";
const [popperRef, popperContent] = createPopperActions();
// Example Popper configuration
const popperOptions = {
placement: "bottom-end",
strategy: "fixed",
modifiers: [{ name: "offset", options: { offset: [0, 10] } }],
};
</script>
<Popover>
<PopoverButton use={[popperRef]}>Solutions</PopoverButton>
<PopoverPanel use={[[popperContent, popperOptions]]}>
<a href="/analytics">Analytics</a>
<a href="/engagement">Engagement</a>
<a href="/security">Security</a>
<a href="/integrations">Integrations</a>
<img src="/solutions.jpg" alt="" />
</PopoverPanel>
</Popover>
```
## Styling
[See here](general-concepts#component-styling) for some general notes on styling the components in this library.
## Showing/hiding the popover
By default, your `PopoverPanel` will be shown/hidden automatically based on the internal open state tracked within the `Popover` component itself.
If you'd rather handle this yourself (perhaps because you need to add an extra wrapper element for one reason or another), you can add a `static` prop to the `PopoverPanel` component to tell it to always render, and use the `open` slot prop provided by the `Popover` to show or hide the popover yourself.
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
</script>
<Popover let:open>
<PopoverButton>Solutions</PopoverButton>
{#if open}
<div>
<!-- Using `static`, `PopoverPanel` is always rendered
and ignores the `open` state. -->
<PopoverPanel static>
<!-- ... -->
</PopoverPanel>
</div>
{/if}
</Popover>
```
## Closing popovers manually
Since popovers can contain interactive content like form controls, we can't automatically close them when you click something inside of them like we can with `Menu` components.
To close a popover manually when clicking a child of its panel, render that child as a `PopoverButton`. You can use the `as` prop to customize which element is being rendered.
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
</script>
<Popover>
<PopoverButton>Solutions</PopoverButton>
<PopoverPanel>
<PopoverButton as="a" href="/insights">Insights</PopoverButton>
<!-- ... -->
</PopoverPanel>
</Popover>
```
Alternatively, `Popover` and `PopoverPanel` expose a `close()` slot prop which you can use to imperatively close the panel:
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
</script>
<Popover>
<PopoverButton>Solutions</PopoverButton>
<PopoverPanel let:close>
<button
on:click={async () => {
await fetch("/accept-terms", { method: "POST" });
close();
}}
>
Read and accept
</button>
<!-- ... -->
</PopoverPanel>
</Popover>
```
By default the `PopoverButton` receives focus after calling `close()`, but you can change this by passing an element into `close(el)`.
## Adding an overlay
If you'd like to style a backdrop over your application UI whenever you open a `Popover`, use the `PopoverOverlay` component:
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverOverlay,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
</script>
<Popover let:open>
<PopoverButton>Solutions</PopoverButton>
<PopoverOverlay
class={open ? "popover-overlay-open" : "popover-overlay-closed"}
/>
<PopoverPanel>
<!-- ... -->
</PopoverPanel>
</Popover>
<style>
/* WARNING: This is just for demonstration.
Using :global() in this way can be risky. */
:global(.popover-overlay-open) {
background-color: rgb(0 0 0);
opacity: 0.3;
position: fixed;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
/* Your styles here */
}
:global(.popover-overlay-closed) {
background-color: rgb(0 0 0);
opacity: 0;
}
</style>
```
Like all the other components, PopoverOverlay is completely unstyled, so how it appears is up to you. In this example, we put the `PopoverOverlay` before the `PopoverPanel` in the DOM so that it doesn't cover up the panel's contents. Also, since the `PopoverOverlay` is always rendered (even when the panel is closed), we use the `open` slot prop to only make it full-screen when the panel is open.
## Transitions
To animate the opening and closing of the popover panel, you can use [this library's Transition component](/docs/1.0/transition) or Svelte's built-in transition engine. See that page for a comparison.
### Using the `Transition` component
To use the `Transition` component, all you need to do is wrap the `PopoverPanel` in a `<Transition>` and the panel will transition automatically.
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
Transition,
} from "@rgossiaux/svelte-headlessui";
</script>
<Popover>
<PopoverButton>Solutions</PopoverButton>
<!-- This example uses Tailwind's transition classes -->
<Transition
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<PopoverPanel>
<!-- ... -->
</PopoverPanel>
</Transition>
</Popover>
```
### Using Svelte transitions
If you wish to animate your popovers using another technique (like Svelte's built-in transitions), you can use the `static` prop to tell the component to not manage rendering itself, so you can control it manually in another way.
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
</script>
<Popover let:open>
<PopoverButton>Solutions</PopoverButton>
{#if open}
<div transition:fade>
<PopoverPanel static>
<!-- ... -->
</PopoverPanel>
</div>
{/if}
</Popover>
```
Without the `static` prop, the exit transitions won't work correctly.
## Grouping related popovers
When rendering several related `Popover`s, for example in a site's header navigation, use the `PopoverGroup` component. This ensures panels stay open while users are tabbing between `Popover`s within a group, but closes any open panel once the user tabs outside of the group:
```svelte
<script>
import {
Popover,
PopoverButton,
PopoverGroup,
PopoverPanel,
} from "@rgossiaux/svelte-headlessui";
</script>
<PopoverGroup>
<Popover>
<PopoverButton>Solutions</PopoverButton>
<PopoverPanel>
<!-- ... -->
</PopoverPanel>
</Popover>
<Popover>
<PopoverButton>Solutions</PopoverButton>
<PopoverPanel>
<!-- ... -->
</PopoverPanel>
</Popover>
</PopoverGroup>
```
## When to use a `Popover`
Here's how `Popover` compares to other components from Headless UI:
- <a href="menu">Menu</a>: `Popover`s are more general-purpose than `Menu`s.
`Menu`s only support very restricted content and have specific accessibility
semantics. Arrow keys also navigate a `Menu`'s items, unlike a `Popover`.
`Menu`s are best for UI elements that resemble things like the menus you'd
find in the title bar of most operating systems. If your floating panel has
images or more markup than simple links, use a Popover.
- <a href="disclosure">Disclosure</a>: `Disclosure`s are useful for things that
typically reflow the document, like an accordion. `Popover`s also have extra
behavior on top of `Disclosure`s: they can render overlays, and are closed
when the user either clicks the overlay (by clicking outside of the
`Popover`'s content) or presses the Escape key. If your UI element needs this
behavior, use a `Popover` instead of a `Disclosure`.
- <a href="dialog">Dialog</a>: `Dialog`s are meant to grab the user's full
attention. They typically render a floating panel in the center of the screen,
and use a backdrop to dim the rest of the application's contents. They also
capture focus and prevent tabbing away from the `Dialog`'s contents until the
`Dialog` is dismissed. Popovers are more contextual, and are usually
positioned near the element that triggered them.
## Accessibility notes
### Focus management
Pressing Tab on an open panel will focus the first focusable element within the panel's contents. If a `PopoverGroup` is being used, Tab cycles from the end of an open panel's content to the next `Popover`'s button.
### Mouse interaction
Clicking a `PopoverButton` toggles a panel open and closed. Clicking anywhere outside of an open panel will close that panel.
### Keyboard interaction
| Command | Description |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `<Enter>` / `<Space>` when a `PopoverButton` is focused | Toggles panel |
| `<Esc>` | Closes any open popovers |
| `<Tab>` | Cycles through an open panel's contents. Tabbing out of an open panel will close that panel, and tabbing from one open panel to a sibling popover's button (within a `PopoverGroup`) closes the first panel |
| `<Shift>` + `<Tab>` | Cycles backwards through an open panel's contents |
### Other
Nested `Popover`s are supported, and all panels will close correctly whenever the root panel is closed.
All relevant ARIA attributes are automatically managed.
## Component API
### Popover
The main popover component.
| Prop | Default | Type | Description |
| ---- | ------- | -------- | ------------------------------------------ |
| `as` | `div` | `string` | The element the `Popover` should render as |
| Slot prop | Type | Description |
| --------- | ---------------------------- | ----------------------------------------------------------------------------- |
| `open` | `boolean` | Whether the popover is open |
| `close` | `(el?: HTMLElement) => void` | Closes the popover and focuses `el`, if passed, or the `PopoverButton` if not |
### PopoverOverlay
This can be used to create an overlay for your popover. Clicking on the overlay will close the popover.
| Prop | Default | Type | Description |
| ---- | ------- | -------- | ------------------------------------------------- |
| `as` | `div` | `string` | The element the `PopoverOverlay` should render as |
| Slot prop | Type | Description |
| --------- | --------- | ---------------------------------- |
| `open` | `boolean` | Whether or not the popover is open |
### PopoverButton
This is the trigger button to toggle a popover.
You can also use this `PopoverButton` component inside a `PopoverPanel`. If you do, it will behave as a close button and have the appropriate `aria-*` attributes.
| Prop | Default | Type | Description |
| ---- | -------- | -------- | ------------------------------------------------ |
| `as` | `button` | `string` | The element the `PopoverButton` should render as |
| Slot prop | Type | Description |
| --------- | --------- | ---------------------------------- |
| `open` | `boolean` | Whether or not the popover is open |
### PopoverPanel
This component contains the contents of your popover.
| Prop | Default | Type | Description |
| --------- | ------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `as` | `div` | `string` | The element the `PopoverPanel` should render as |
| `focus` | `false` | `boolean` | Whether the `PopoverPanel` should trap focus. If `true`, focus will move inside the `PopoverPanel` when it is opened, and if focus leaves the `PopoverPanel` it will close. |
| `static` | `false` | `boolean` | Whether the element should ignore the internally managed open/closed state |
| `unmount` | `true` | `boolean` | Whether the element should be unmounted, instead of just hidden, based on the open/closed state |
Note that `static` and `unmount` cannot be used together.
| Slot prop | Type | Description |
| --------- | ---------------------------- | ----------------------------------------------------------------------------- |
| `open` | `boolean` | Whether or not the popover is open |
| `close` | `(el?: HTMLElement) => void` | Closes the popover and focuses `el`, if passed, or the `PopoverButton` if not |
### PopoverGroup
This component links related `Popover`s. Tabbing out of one `PopoverPanel` will focus the next popover's `PopverButton`, and tabbing outside of the `PopoverGroup` completely will close all popovers inside the group.
| Prop | Default | Type | Description |
| ---- | ------- | -------- | ----------------------------------------------- |
| `as` | `div` | `string` | The element the `PopoverGroup` should render as |