Popover
A popover element that can display rich content in a portal, triggered by a button.
+
Tailwind and Alpine
Copied!
Dimensions
Set the dimensions for the layer.
<div x-data="{
popoverOpen: false,
popoverArrow: true,
popoverPosition: 'bottom',
popoverHeight: 0,
popoverOffset: 8,
popoverHeightCalculate() {
this.$refs.popover.classList.add('invisible');
this.popoverOpen=true;
let that=this;
$nextTick(function(){
that.popoverHeight = that.$refs.popover.offsetHeight;
that.popoverOpen=false;
that.$refs.popover.classList.remove('invisible');
that.$refs.popoverInner.setAttribute('x-transition', '');
that.popoverPositionCalculate();
});
},
popoverPositionCalculate(){
if(window.innerHeight < (this.$refs.popoverButton.getBoundingClientRect().top + this.$refs.popoverButton.offsetHeight + this.popoverOffset + this.popoverHeight)){
this.popoverPosition = 'top';
} else {
this.popoverPosition = 'bottom';
}
}
}"
x-init="
that = this;
window.addEventListener('resize', function(){
popoverPositionCalculate();
});
$watch('popoverOpen', function(value){
if(value){ popoverPositionCalculate(); document.getElementById('width').focus(); }
});
"
class="relative">
<button x-ref="popoverButton" @click="popoverOpen=!popoverOpen" class="flex items-center justify-center w-10 h-10 bg-white border rounded-full shadow-sm cursor-pointer hover:bg-neutral-100 focus-visible:ring-gray-400 focus-visible:ring-2 focus-visible:outline-none active:bg-white border-neutral-200/70">
<svg class="w-4 h-4" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.5 3C4.67157 3 4 3.67157 4 4.5C4 5.32843 4.67157 6 5.5 6C6.32843 6 7 5.32843 7 4.5C7 3.67157 6.32843 3 5.5 3ZM3 5C3.01671 5 3.03323 4.99918 3.04952 4.99758C3.28022 6.1399 4.28967 7 5.5 7C6.71033 7 7.71978 6.1399 7.95048 4.99758C7.96677 4.99918 7.98329 5 8 5H13.5C13.7761 5 14 4.77614 14 4.5C14 4.22386 13.7761 4 13.5 4H8C7.98329 4 7.96677 4.00082 7.95048 4.00242C7.71978 2.86009 6.71033 2 5.5 2C4.28967 2 3.28022 2.86009 3.04952 4.00242C3.03323 4.00082 3.01671 4 3 4H1.5C1.22386 4 1 4.22386 1 4.5C1 4.77614 1.22386 5 1.5 5H3ZM11.9505 10.9976C11.7198 12.1399 10.7103 13 9.5 13C8.28967 13 7.28022 12.1399 7.04952 10.9976C7.03323 10.9992 7.01671 11 7 11H1.5C1.22386 11 1 10.7761 1 10.5C1 10.2239 1.22386 10 1.5 10H7C7.01671 10 7.03323 10.0008 7.04952 10.0024C7.28022 8.8601 8.28967 8 9.5 8C10.7103 8 11.7198 8.8601 11.9505 10.0024C11.9668 10.0008 11.9833 10 12 10H13.5C13.7761 10 14 10.2239 14 10.5C14 10.7761 13.7761 11 13.5 11H12C11.9833 11 11.9668 10.9992 11.9505 10.9976ZM8 10.5C8 9.67157 8.67157 9 9.5 9C10.3284 9 11 9.67157 11 10.5C11 11.3284 10.3284 12 9.5 12C8.67157 12 8 11.3284 8 10.5Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
</button>
<div x-ref="popover"
x-show="popoverOpen"
x-init="setTimeout(function(){ popoverHeightCalculate(); }, 100);"
x-trap.inert="popoverOpen"
@click.away="popoverOpen=false;"
@keydown.escape.window="popoverOpen=false"
:class="{ 'top-0 mt-12' : popoverPosition == 'bottom', 'bottom-0 mb-12' : popoverPosition == 'top' }"
class="absolute w-[300px] max-w-lg -translate-x-1/2 left-1/2" x-cloak>
<div x-ref="popoverInner" x-show="popoverOpen" class="w-full p-4 bg-white border rounded-md shadow-sm border-neutral-200/70">
<div x-show="popoverArrow && popoverPosition == 'bottom'" class="absolute top-0 inline-block w-5 mt-px overflow-hidden -translate-x-2 -translate-y-2.5 left-1/2"><div class="w-2.5 h-2.5 origin-bottom-left transform rotate-45 bg-white border-t border-l rounded-sm"></div></div>
<div x-show="popoverArrow && popoverPosition == 'top'" class="absolute bottom-0 inline-block w-5 mb-px overflow-hidden -translate-x-2 translate-y-2.5 left-1/2"><div class="w-2.5 h-2.5 origin-top-left transform -rotate-45 bg-white border-b border-l rounded-sm"></div></div>
<div class="grid gap-4">
<div class="space-y-2">
<h4 class="font-medium leading-none">Dimensions</h4>
<p class="text-sm text-muted-foreground">Set the dimensions for the layer.</p>
</div>
<div class="grid gap-2">
<div class="grid items-center grid-cols-3 gap-4"><label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="width">Width</label><input class="flex w-full h-8 col-span-2 px-3 py-2 text-sm bg-transparent border rounded-md border-input ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" id="width" value="100%"></div>
<div class="grid items-center grid-cols-3 gap-4"><label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="maxWidth">Max. width</label><input class="flex w-full h-8 col-span-2 px-3 py-2 text-sm bg-transparent border rounded-md border-input ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" id="maxWidth" value="300px"></div>
<div class="grid items-center grid-cols-3 gap-4"><label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="height">Height</label><input class="flex w-full h-8 col-span-2 px-3 py-2 text-sm bg-transparent border rounded-md border-input ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" id="height" value="25px"></div>
<div class="grid items-center grid-cols-3 gap-4"><label class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" for="maxHeight">Max. height</label><input class="flex w-full h-8 col-span-2 px-3 py-2 text-sm bg-transparent border rounded-md border-input ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" id="maxHeight" value="none"></div>
</div>
</div>
</div>
</div>
</div>
Alpine Focus Plugin
This element uses the AlpineJS focus plug-in, which will trap the focus inside the popover when opened and return focus to the trigger when closed.
Data
Below you will find the data properties available in the x-data
attribute of this element.
Property and Description | Description |
---|---|
popoverOpen | A boolean value that will toggle the popover open or close. |
A boolean value that will toggle the popover open or close. | |
popoverArrow | A boolean value that will show or hide the popover arrow |
A boolean value that will show or hide the popover arrow | |
popoverPosition | A value that will indicate whether the popover should be at the bottom or top. |
A value that will indicate whether the popover should be at the bottom or top. | |
popoverHeight | The height of the popover. This will be calculated on initialize. |
The height of the popover. This will be calculated on initialize. | |
popoverOffset | An offset value in pixels that will be applied to the popover position. |
An offset value in pixels that will be applied to the popover position. | |
popoverHeightCalculate() | A method that will calculate the height of the popover. |
A method that will calculate the height of the popover. | |
popoverPositionCalculate() | A method that will calculate whether the popover should be at top or bottom. |
A method that will calculate whether the popover should be at top or bottom. |