Popover

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

用法

在气泡卡片的默认插槽中使用 Button 或任何其他组件。

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

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

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

模式

使用 mode prop 来更改气泡卡片的模式。默认为 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 UIHoverCard组件将被使用,而不是Popover.

延迟

使用 hover 模式时,您可以使用 open-delayclose-delay prop 来控制气泡卡片打开或关闭前的延迟。

<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 prop 来控制气泡卡片内容的渲染方式,例如其 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 prop 在气泡卡片上显示箭头。

<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 来切换气泡卡片。

禁用关闭

dismissible prop 设置为 false 可防止在点击气泡卡片外部或按 Esc 键时关闭它。当用户尝试关闭时,将触发一个 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>

带命令面板

您可以在气泡卡片的内容中使用 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>

随光标移动

您可以通过使用以下方式,使气泡卡片在悬停于元素上方时跟随光标移动reference属性

<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 插槽将气泡卡片定位到自定义元素。

此插槽仅在 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

属性

属性默认值类型
模式

'click'

"click" | "hover"

气泡卡片的显示模式。

内容

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

PopoverContentProps & Partial<EmitsToProps<PopoverContentImplEmits>>

气泡卡片的内容。

arrow

false

boolean | PopoverArrowProps

在气泡卡片旁边显示箭头。

portal

true

string | false | true | HTMLElement

在 portal 中渲染气泡卡片。

reference

Element | VirtualElement

用于定位的引用(或锚点)元素。

如果未提供,将使用当前组件作为锚点。

可关闭的

true

boolean

false 时,点击外部或按 Esc 键时气泡卡片不会关闭。

defaultOpen

boolean

气泡卡片初始渲染时的打开状态。当您不需要控制其打开状态时使用。

open

boolean

气泡卡片的受控打开状态。

modal

false

boolean

气泡卡片的模态性。当设置为 true 时,与外部元素的交互将被禁用,并且只有气泡卡片内容对屏幕阅读器可见。

openDelay

0

number

鼠标进入触发器到悬停卡片打开的持续时间。

closeDelay

0

number

鼠标离开触发器或内容到悬停卡片关闭的持续时间。

ui

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

插槽

插槽类型
default

{ open: boolean; }

内容

{}

anchor

{}

事件

事件类型
close:prevent

[]

update:open

[value: boolean]

主题

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'
          }
        }
      }
    })
  ]
})