Skip to content

UIPopover

UIPopover

A versatile popover component that displays floating content when triggered by user interaction. Supports multiple trigger modes, placements, and customizable content.

<template>
<UIPopover
:trigger="'click'"
:placement="'bottom'"
:title="'Popover Title'"
:width="300"
:showArrow="true"
:keepAliveOnHover="true"
@update:show="handleShowChange"
>
<template #trigger>
<UIButton>
Click me
</UIButton>
</template>
<div class="p-4">
<h3 class="text-lg font-medium mb-2">
Content Title
</h3>
<p class="text-gray-600">
Popover content goes here.
</p>
</div>
</UIPopover>
</template>
<script setup lang="ts">
const handleShowChange = (visible: boolean) => {
console.log('Popover visibility:', visible)
}
</script>

Props

  • trigger (‘click’ | ‘hover’ | ‘focus’ | ‘manual’): Trigger mode
  • placement (‘top’ | ‘top-start’ | ‘top-end’ | ‘bottom’ | ‘bottom-start’ | ‘bottom-end’ | ‘left’ | ‘left-start’ | ‘left-end’ | ‘right’ | ‘right-start’ | ‘right-end’): Popover placement
  • title (string): Popover title
  • width (number | string): Popover width
  • showArrow (boolean): Show popover arrow
  • disabled (boolean): Disable popover
  • arrowPointToCenter (boolean): Arrow points to center
  • keepAliveOnHover (boolean): Keep popover open on hover
  • zIndex (number): Popover z-index
  • delay (number): Show/hide delay in milliseconds
  • offset ([number, number]): Offset from trigger
  • hideOnClickOutside (boolean): Hide when clicking outside
  • appendToBody (boolean): Append to document body
  • show (boolean): Control visibility manually
  • transition (string): Custom transition name

Events

  • update:show: Emitted when popover visibility changes
  • show: Emitted when popover shows
  • hide: Emitted when popover hides
  • clickOutside: Emitted when clicking outside
  • beforeEnter: Emitted before enter transition
  • afterEnter: Emitted after enter transition
  • beforeLeave: Emitted before leave transition
  • afterLeave: Emitted after leave transition

Slots

  • trigger: Custom trigger content
  • default: Popover content
  • title: Custom title content
  • arrow: Custom arrow content

Usage Examples

  1. Basic Popover:
<template>
<UIPopover
trigger="hover"
placement="top"
title="Quick Info"
>
<template #trigger>
<InfoIcon class="w-5 h-5 text-gray-400" />
</template>
<div class="p-3">
Hover content with information
</div>
</UIPopover>
</template>
  1. Complex Content with Actions:
<template>
<UIPopover
trigger="click"
placement="bottom-start"
:width="320"
:keepAliveOnHover="true"
>
<template #trigger>
<UIButton>
User Settings
</UIButton>
</template>
<div class="divide-y">
<div class="p-4">
<div class="flex items-center space-x-3">
<img
:src="user.avatar"
class="w-10 h-10 rounded-full"
/>
<div>
<div class="font-medium">
{{ user.name }}
</div>
<div class="text-sm text-gray-500">
{{ user.email }}
</div>
</div>
</div>
</div>
<div class="p-2">
<UIButton
v-for="action in actions"
:key="action.id"
variant="ghost"
class="w-full justify-start"
@click="action.handler"
>
<component
:is="action.icon"
class="w-5 h-5 mr-2"
/>
{{ action.label }}
</UIButton>
</div>
</div>
</UIPopover>
</template>
<script setup>
const actions = [
{
id: 'profile',
label: 'Edit Profile',
icon: UserIcon,
handler: () => navigateToProfile()
},
{
id: 'settings',
label: 'Settings',
icon: CogIcon,
handler: () => openSettings()
},
{
id: 'logout',
label: 'Logout',
icon: LogoutIcon,
handler: () => logout()
}
]
</script>
  1. Form in Popover:
<template>
<UIPopover
v-model:show="showForm"
trigger="manual"
placement="right"
:width="400"
:hideOnClickOutside="false"
>
<template #trigger>
<UIButton @click="showForm = true">
Quick Edit
</UIButton>
</template>
<form
class="p-4"
@submit.prevent="handleSubmit"
>
<h3 class="text-lg font-medium mb-4">
Edit Details
</h3>
<div class="space-y-4">
<UIInput
v-model="form.name"
label="Name"
required
/>
<UIInput
v-model="form.email"
label="Email"
type="email"
required
/>
<div class="flex justify-end space-x-2">
<UIButton
variant="ghost"
@click="showForm = false"
>
Cancel
</UIButton>
<UIButton
type="primary"
:loading="saving"
>
Save Changes
</UIButton>
</div>
</div>
</form>
</UIPopover>
</template>

Best Practices

  1. Positioning:

    • Choose appropriate placement
    • Consider viewport edges
    • Maintain visibility
    • Handle overflow
  2. User Experience:

    • Clear trigger indication
    • Appropriate timing
    • Smooth transitions
    • Proper focus management
  3. Accessibility:

    • ARIA attributes
    • Keyboard navigation
    • Focus trapping
    • Screen reader support
  4. Performance:

    • Lazy content loading
    • Efficient updates
    • Memory management
    • Event cleanup

Common Use Cases

  1. Tooltips with Actions:
<template>
<UIPopover
trigger="hover"
placement="top"
:delay="200"
>
<template #trigger>
<div class="text-primary-600 underline">
{{ value }}
</div>
</template>
<div class="p-2">
<UIButton
size="sm"
variant="ghost"
@click="copyToClipboard"
>
<CopyIcon class="w-4 h-4 mr-1" />
Copy
</UIButton>
</div>
</UIPopover>
</template>
  1. Context Menu:
<template>
<UIPopover
v-model:show="showMenu"
trigger="manual"
:placement="menuPlacement"
>
<template #trigger>
<div
class="w-full"
@contextmenu.prevent="showContextMenu"
>
{{ content }}
</div>
</template>
<div class="py-1">
<UIButton
v-for="option in menuOptions"
:key="option.id"
variant="ghost"
class="w-full justify-start px-4 py-2"
@click="option.handler"
>
{{ option.label }}
</UIButton>
</div>
</UIPopover>
</template>
<script setup>
const showMenu = ref(false)
const menuPlacement = ref('bottom')
const showContextMenu = (event: MouseEvent) => {
// Calculate placement based on click position
const { clientX, clientY } = event
menuPlacement.value = calculatePlacement(clientX, clientY)
showMenu.value = true
}
</script>
  1. Multi-Level Navigation:
<template>
<UIPopover
v-for="menu in menus"
:key="menu.id"
trigger="hover"
:placement="menu.placement"
:delay="100"
>
<template #trigger>
<UIButton variant="ghost">
{{ menu.label }}
<ChevronRightIcon
v-if="menu.children"
class="w-4 h-4 ml-2"
/>
</UIButton>
</template>
<div class="py-1 min-w-[200px]">
<template v-if="menu.children">
<UIPopover
v-for="child in menu.children"
:key="child.id"
trigger="hover"
placement="right-start"
:delay="100"
>
<template #trigger>
<UIButton
variant="ghost"
class="w-full justify-between px-4 py-2"
>
{{ child.label }}
<ChevronRightIcon
v-if="child.children"
class="w-4 h-4"
/>
</UIButton>
</template>
<div class="py-1 min-w-[200px]">
<UIButton
v-for="subItem in child.children"
:key="subItem.id"
variant="ghost"
class="w-full justify-start px-4 py-2"
@click="subItem.handler"
>
{{ subItem.label }}
</UIButton>
</div>
</UIPopover>
</template>
</div>
</UIPopover>
</template>