Lzh on GitHub
一个非模态对话框,围绕触发元素浮动。

用法

在 Popover 的默认插槽中使用 Button 或任何其他组件。

然后,使用 #content 插槽添加 Popover 打开时显示的内容。

<template>
  <UPopover>
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="size-48 m-4 inline-flex" />
    </template>
  </UPopover>
</template>

模式 (Mode)

使用 mode prop 更改 Popover 的模式。默认为 click

<template>
  <UPopover mode="hover">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="size-48 m-4 inline-flex" />
    </template>
  </UPopover>
</template>
当使用 hover 模式时,将使用 Reka UI 的 HoverCard 组件而不是 Popover 组件。

延迟 (Delay)

当使用 hover 模式时,你可以使用 open-delayclose-delay props 控制 Popover 打开或关闭前的延迟。

<template>
  <UPopover mode="hover" :open-delay="500" :close-delay="300">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="size-48 m-4 inline-flex" />
    </template>
  </UPopover>
</template>

内容 (Content)

使用 content prop 控制 Popover 内容的渲染方式,例如其 alignside

<template>
  <UPopover
    :content="{
      align: 'center',
      side: 'bottom',
      sideOffset: 8
    }"
  >
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="size-48 m-4 inline-flex" />
    </template>
  </UPopover>
</template>

箭头 (Arrow)

使用 arrow prop 在 Popover 上显示一个箭头。

<template>
  <UPopover arrow>
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="size-48 m-4 inline-flex" />
    </template>
  </UPopover>
</template>

示例

控制打开状态

你可以通过使用 default-open prop 或 v-model:open 指令来控制打开状态。

<script setup lang="ts">
const open = ref(false)

defineShortcuts({
  o: () => open.value = !open.value
})
</script>

<template>
  <UPopover v-model:open="open">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="size-48 m-4 inline-flex" />
    </template>
  </UPopover>
</template>
在此示例中,利用 defineShortcuts,你可以通过按下 O 来切换 Popover。
即使你不需要用户手动触发显示,也不能省略 trigger slot。否则组件无法正确渲染、定位和绑定弹出层逻辑。

禁用 dismissal

dismissible prop 设置为 false,以防止通过点击 Popover 外部或按 escape 键关闭 Popover。当用户尝试关闭时,将发出 close:prevent 事件。

<script setup lang="ts">
const open = ref(false)
</script>

<template>
  <UPopover v-model:open="open" :dismissible="false" :ui="{ content: 'p-4' }">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <div class="flex items-center gap-4 mb-4">
        <h2 class="text-highlighted font-semibold">
          Popover non-dismissible
        </h2>

        <UButton color="neutral" variant="ghost" icon="i-lucide-x" @click="open = false" />
      </div>

      <Placeholder class="size-full min-h-48" />
    </template>
  </UPopover>
</template>

带命令面板

你可以在 Popover 的内容中使用 CommandPalette 组件。

<script setup lang="ts">
const items = ref([
  {
    label: 'bug',
    value: 'bug',
    chip: {
      color: 'error' as const
    }
  },
  {
    label: 'feature',
    value: 'feature',
    chip: {
      color: 'success' as const
    }
  },
  {
    label: 'enhancement',
    value: 'enhancement',
    chip: {
      color: 'info' as const
    }
  }
])
const label = ref([])
</script>

<template>
  <UPopover :content="{ side: 'right', align: 'start' }">
    <UButton
      icon="i-lucide-tag"
      label="Select labels"
      color="neutral"
      variant="subtle"
    />

    <template #content>
      <UCommandPalette
        v-model="label"
        multiple
        placeholder="Search labels..."
        :groups="[{ id: 'labels', items }]"
        :ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }"
      />
    </template>
  </UPopover>
</template>

跟随光标 New

当鼠标悬停在元素上时,你可以使用 reference 属性让 Popover 跟随光标。

<script setup lang="ts">
const open = ref(false)
const anchor = ref({ x: 0, y: 0 })

const reference = computed(() => ({
  getBoundingClientRect: () =>
    ({
      width: 0,
      height: 0,
      left: anchor.value.x,
      right: anchor.value.x,
      top: anchor.value.y,
      bottom: anchor.value.y,
      ...anchor.value
    } as DOMRect)
}))
</script>

<template>
  <UPopover
    :open="open"
    :reference="reference"
    :content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
  >
    <div
      class="flex items-center justify-center rounded-md border border-dashed border-accented text-sm aspect-video w-72"
      @pointerenter="open = true"
      @pointerleave="open = false"
      @pointermove="(ev) => {
        anchor.x = ev.clientX
        anchor.y = ev.clientY
      }"
    >
      Hover me
    </div>

    <template #content>
      <div class="p-4">
        {{ anchor.x.toFixed(0) }} - {{ anchor.y.toFixed(0) }}
      </div>
    </template>
  </UPopover>
</template>

带锚点插槽

你可以使用 #anchor 插槽将 Popover 定位到自定义元素。

此插槽仅在 modeclick 时有效。
<script lang="ts" setup>
const open = ref(false)
</script>

<template>
  <UPopover
    v-model:open="open"
    :dismissible="false"
    :ui="{ content: 'w-(--reka-popper-anchor-width) p-4' }"
  >
    <template #anchor>
      <UInput placeholder="Focus to open" @focus="open = true" @blur="open = false" />
    </template>

    <template #content>
      <Placeholder class="w-full aspect-square" />
    </template>
  </UPopover>
</template>

API

Props

Prop Default Type
mode

'click'

"click" | "hover"

The display mode of the popover.

content

{ side: 'bottom', sideOffset: 8, collisionPadding: 8 }

PopoverContentProps & Partial<EmitsToProps<PopoverContentImplEmits>>

The content of the popover.

arrow

false

boolean | PopoverArrowProps

Display an arrow alongside the popover.

portal

true

string | false | true | HTMLElement

Render the popover in a portal.

reference

Element | VirtualElement

The reference (or anchor) element that is being referred to for positioning.

If not provided will use the current component as anchor.

dismissible

true

boolean

When false, the popover will not close when clicking outside or pressing escape.

defaultOpen

boolean

The open state of the popover 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.

modal

false

boolean

The modality of the popover. When set to true, interaction with outside elements will be disabled and only popover content will be visible to screen readers.

openDelay

0

number

The duration from when the mouse enters the trigger until the hover card opens.

closeDelay

0

number

The duration from when the mouse leaves the trigger or content until the hover card closes.

ui

{ content?: ClassNameValue; arrow?: ClassNameValue; }

Slots

Slot Type
default

{ open: boolean; }

content

{}

anchor

{}

Emits

Event Type
update:open

[value: boolean]

close:prevent

[]

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    popover: {
      slots: {
        content: 'bg-default shadow-lg rounded-md ring ring-default data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-popover-content-transform-origin) focus:outline-none pointer-events-auto',
        arrow: 'fill-default'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        popover: {
          slots: {
            content: 'bg-default shadow-lg rounded-md ring ring-default data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-popover-content-transform-origin) focus:outline-none pointer-events-auto',
            arrow: 'fill-default'
          }
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      ui: {
        popover: {
          slots: {
            content: 'bg-default shadow-lg rounded-md ring ring-default data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in] origin-(--reka-popover-content-transform-origin) focus:outline-none pointer-events-auto',
            arrow: 'fill-default'
          }
        }
      }
    })
  ]
})