トリガー要素の周りに浮かぶ非モーダルダイアログ。

使用法

ポップオーバーのデフォルトスロットにある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

ポップオーバーのモードを変更するには、modeプロパティを使用します。デフォルトはclickです。

hoverモードでは、ユーザーがタッチデバイスのトリガーをタップしてポップオーバーを切り替えられるようにenable-touchプロパティを設定するか、タップするトリガーにclickモードを使用します。
<template>
  <UPopover mode="hover" enable-touch>
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="size-48 m-4 inline-flex" />
    </template>
  </UPopover>
</template>
hoverモードを使用する場合、Popoverの代わりにReka UI HoverCardコンポーネントが使用されます。

Delay

hoverモードを使用する場合、open-delayとclose-delayプロップを使用して、ポップオーバーを開くか閉じる前のディレイを制御できます。

<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プロパティを使用して、alignやsideなど、Popoverコンテンツのレンダリング方法を制御します。

<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プロパティを使用して、ポップオーバーに矢印を表示します。

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

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

modalプロパティを使用して、Popoverが外部コンテンツとのインタラクションをブロックするかどうかを制御します。デフォルトはfalseです。

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

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

Dismissible

dismissibleプロパティを使用して、ポップオーバーの外側をクリックするか、escapeを押したときにポップオーバーを拒否するかどうかを制御します。デフォルトはtrueです。

close:preventイベントは、ユーザーがクローズしようとすると発行されます。
<template>
  <UPopover :dismissible="false" :ui="{ content: 'p-4' }">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content="{ close }">
      <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="close" />
      </div>

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

例

Controlオープンステート

オープン状態を制御するには、default-openプロパティまたは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>
<script setup lang="ts">
import { ref } from 'vue'

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を活用して、kbd{value="O"}を押してポップオーバーを切り替えることができます。

Withコマンドパレット

Popoverのコンテンツ内で CommandPaletteコンポーネントを使用できます。

<script setup lang="ts">
import type { CommandPaletteItem } from '@nuxt/ui'

const items = ref([
  {
    label: 'bug',
    value: 'bug',
    chip: {
      color: 'error'
    }
  },
  {
    label: 'feature',
    value: 'feature',
    chip: {
      color: 'success'
    }
  },
  {
    label: 'enhancement',
    value: 'enhancement',
    chip: {
      color: 'info'
    }
  }
] satisfies CommandPaletteItem[])

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>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteItem } from '@nuxt/ui'

const items = ref([
  {
    label: 'bug',
    value: 'bug',
    chip: {
      color: 'error'
    }
  },
  {
    label: 'feature',
    value: 'feature',
    chip: {
      color: 'success'
    }
  },
  {
    label: 'enhancement',
    value: 'enhancement',
    chip: {
      color: 'info'
    }
  }
] satisfies CommandPaletteItem[])

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: PointerEvent) => {
        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>
<script setup lang="ts">
import { ref, computed } from 'vue'

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: PointerEvent) => {
        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スロットを使用して、ポップオーバーをカスタム要素に対して配置できます。

このスロットはmodeがclickの場合にのみ動作します。
<script lang="ts" setup>
const open = ref(false)
</script>

<template>
  <UPopover
    v-model:open="open"
    :dismissible="false"
    :ui="{ content: 'w-(--reka-popover-trigger-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'
M

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. { rounded: true }

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.

Accepts an element or a virtual element (anything with getBoundingClientRect), and can be changed reactively to re-anchor the popover (e.g. for a guided tour). 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.

enableTouch
boolean

When true, tapping the trigger on touch devices toggles the hover card open/closed. By default touch interactions are ignored to match pointer hover semantics.

ui
{ content?: SlotClass; arrow?: SlotClass; }

スロット

Slot Type
default
{ open: boolean; }
content
SlotProps<M>
anchor
SlotProps<M>
なぜなら、Reka UIはPopoverに対してこれを公開し、HoverCardに対しては公開しないからです。

Emits

Event Type
close:prevent
[]
update:open
[value: boolean]

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_var(--ease-out)] data-[state=closed]:animate-[scale-out_100ms_var(--ease-out)] origin-(--reka-popover-content-transform-origin) focus:outline-none pointer-events-auto',
        arrow: 'fill-bg stroke-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_var(--ease-out)] data-[state=closed]:animate-[scale-out_100ms_var(--ease-out)] origin-(--reka-popover-content-transform-origin) focus:outline-none pointer-events-auto',
            arrow: 'fill-bg stroke-default'
          }
        }
      }
    })
  ]
})

Changelog

No recent changes