Popover
Popover can be used for dropdowns and other popovers when an element is clicked and focused. You have following types of trigger as an option:
- trigger on focus with CSS
trigger-focus
class - trigger on hover with CSS
trigger-hover
class - trigger on click with JS and the
[popover]
attribute
Dependencies
- floating-ui - for
[popover]
trigger
Usage
@import "winduum/src/components/popover/index.css" layer(components);
<div class="x-popover trigger-focus">
<div role="button" class="x-button" tabindex="0">Popover</div>
<div class="x-popover-content center shadow dark:bg-body-secondary mt-2.5 p-2 w-32 flex flex-col">
<button class="x-button ghosted accent-main justify-start w-full">Item 1</button>
<button class="x-button ghosted accent-main justify-start w-full">Item 2</button>
</div>
</div>
<script setup>
import { ref } from 'vue'
import { Popover, PopoverContent } from '@/components/popover'
import { Button } from '@/components/button'
</script>
<template>
<Popover>
<Button>Open Popover</Button>
<PopoverContent>
Popover content
</PopoverContent>
</Popover>
</template>
Variants
Tokens
Applicable to x-popover-content
bottom bottom-start
bottom bottom-end
top top-start
top top-end
right right-start
right right-end
left left-start
left left-end
bottom inline-center
top inline-center
right block-center
left block-center
Installation
Follow instructions for individual framework usage below
Examples
focus-trigger
<div class="x-popover trigger-focus">
<div role="button" class="x-button" tabindex="0">Popover</div>
<div class="x-popover-content center shadow dark:bg-body-secondary mt-2.5 p-2 w-32 flex flex-col">
<button class="x-button ghosted accent-main justify-start w-full">Item 1</button>
<button class="x-button ghosted accent-main justify-start w-full">Item 2</button>
</div>
</div>
hover-trigger
<div class="x-popover trigger-hover">
<div role="button" class="x-button" tabindex="0">Popover</div>
<div class="x-popover-content center shadow dark:bg-body-secondary mt-2.5 p-2 w-32 flex flex-col">
<button class="x-button ghosted accent-main justify-start w-full">Item 1</button>
<button class="x-button ghosted accent-main justify-start w-full">Item 2</button>
</div>
</div>
[popover]
This is using advantages of Popover API with floating-ui. It's also backwards compatible as it's leveraging only the showPopover
and hidePopover
for the top-layer support.
Popover is placed dynamically upon available space, and auto updates itself when needed.
<div class="x-popover w-full flex flex-col items-center">
<button class="x-button" popovertargetaction="toggle" popovertarget="popover">Popover</button>
<div class="x-popover-content shadow dark:bg-body-secondary p-2" id="popover" popover="manual">
<input type="text" autofocus>
</div>
</div>
JavaScript API
showPopover
- Type:
(element: HTMLElement | Element, options?: ShowPopoverOptions) => Promise<void>
- Kind:
async
Example
import { showPopover, hidePopover } from '/src/components/popover'
const popoverActionElement = document?.querySelector('[popovertargetaction="show"]')
popoverActionElement?.addEventListener('click', async (e) => {
e.preventDefault()
const currentTarget = e.currentTarget
await showPopover(currentTarget, {
placement: 'right-end',
})
})
// close on esc
window.addEventListener('keydown', ({ key }) => {
if (key === 'Escape') {
hidePopover(popoverActionElement)
}
})
// outside dismiss
window.addEventListener('click', ({ target }) => {
if (!window.popover?.contains(target) && !popoverActionElement?.isEqualNode(target) && popoverActionElement?.ariaExpanded === 'true') {
hidePopover(popoverActionElement)
}
})
ShowPopoverOptions
anchorSelector
- Type:
string
- Default:
undefined
By default, the anchor selector is the trigger button, you can change this to other selector.
openAttribute
- Type:
string
- Default:
data-open
A string representing an attribute that will be added when popover is visible.
compute
- Type:
boolean
- Default:
true
Determines if the popover should be anchored and computed with @floating-ui/dom
placement
- Type:
Placement
- Default:
undefined
Determines placement of the popover with @floating-ui/dom
, also adds a corresponding class to the popover target.
middleware
- Type:
Array<Middleware | null | undefined | false>
- Default:
[offset(12 ?? options?.offset), flip(options?.flip), shift({ padding: 8, ...options?.shift })]
Customize middleware for @floating-ui/dom
offset
- Type:
OffsetOptions
- Default:
12
Customize offset options for @floating-ui/dom
flip
- Type:
FlipOptions
- Default:
undefined
Customize flip options for @floating-ui/dom
shift
- Type:
ShiftOptions
- Default:
undefined
Customize shift options for @floating-ui/dom
hidePopover
- Type:
(element: HTMLElement | Element) => Promise<void>
- Kind:
async
Example
import { hidePopover } from '/src/components/popover'
const popoverActionElement = document?.querySelector('[popovertargetaction="hide"]')
popoverActionElement?.addEventListener('click', async (e) => {
e.preventDefault()
const currentTarget = e.currentTarget
await hidePopover(currentTarget)
})
togglePopover
- Type:
(element: HTMLElement | Element, options?: ShowPopoverOptions) => Promise<void>
- Kind:
async
Example
import { togglePopover, hidePopover } from '/src/components/popover'
const popoverActionElement = document?.querySelector('[popovertargetaction="toggle"]')
popoverActionElement?.addEventListener('click', async (e) => {
e.preventDefault()
const currentTarget = e.currentTarget
await togglePopover(currentTarget)
})
// close on esc
window.addEventListener('keydown', ({ key }) => {
if (key === 'Escape') {
hidePopover(popoverActionElement)
}
})
// outside dismiss
window.addEventListener('click', ({ target }) => {
if (!window.popover?.contains(target) && !popoverActionElement?.isEqualNode(target) && popoverActionElement?.ariaExpanded === 'true') {
hidePopover(popoverActionElement)
}
})