Popover

弹出框非常适合浮动面板,包含任意内容,例如导航菜单、移动菜单和飞出菜单。

要开始,请通过 npm 安装无头 UI。

请注意,**此库仅支持 Vue 3**。

npm install @headlessui/vue

弹出框使用 PopoverPopoverButtonPopoverPanel 组件构建。

单击 PopoverButton 会自动打开/关闭 PopoverPanel。当面板打开时,单击面板内容以外的任何位置、按 Esc 键或从面板中 Tab 键退出都会关闭弹出框。

<template> <Popover class="relative"> <PopoverButton>Solutions</PopoverButton> <PopoverPanel class="absolute z-10"> <div class="grid grid-cols-2"> <a href="/analytics">Analytics</a> <a href="/engagement">Engagement</a> <a href="/security">Security</a> <a href="/integrations">Integrations</a> </div> <img src="/solutions.jpg" alt="" /> </PopoverPanel> </Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' </script>

这些组件完全没有样式,因此如何样式化 Popover 由您决定。在我们的示例中,我们对 PopoverPanel 使用绝对定位,以将其定位在 PopoverButton 附近,并且不会干扰正常的文档流。

无头 UI 会跟踪有关每个组件的大量状态,例如当前选中的列表框选项、弹出框是打开还是关闭,或者当前通过键盘激活的弹出框中的项目。

但是,由于组件是无头的,并且在开箱即用时完全没有样式,因此您无法看到此信息在您的 UI 中,直到您自己提供每个状态所需的样式。

每个组件都通过 插槽属性 公开有关其当前状态的信息,您可以使用这些属性有条件地应用不同的样式或渲染不同的内容。

例如,Popover 组件公开了 open 状态,该状态告诉您弹出框当前是否打开。

<template>
<Popover v-slot="{ open }">
<!-- Use the `open` state to conditionally change the direction of the chevron icon. --> <PopoverButton> Solutions
<ChevronDownIcon :class="{ 'rotate-180 transform': open }" />
</PopoverButton> <PopoverPanel> <a href="/insights">Insights</a> <a href="/automations">Automations</a> <a href="/reports">Reports</a> </PopoverPanel> </Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' import { ChevronDownIcon } from '@heroicons/vue/20/solid' </script>

有关所有可用插槽属性的完整列表,请参阅 组件 API 文档

每个组件还通过 data-headlessui-state 属性公开有关其当前状态的信息,您可以使用该属性有条件地应用不同的样式。

插槽属性 API 中的任何状态为 true 时,它们将在此属性中列出为以空格分隔的字符串,以便您可以使用 CSS 属性选择器[attr~=value] 形式进行定位。

例如,以下是弹出框打开时 Popover 组件的渲染内容

<!-- Rendered `Popover` --> <div data-headlessui-state="open"> <button data-headlessui-state="open">Solutions</button> <div data-headlessui-state="open"> <a href="/insights">Insights</a> <a href="/automations">Automations</a> <a href="/reports">Reports</a> </div> </div>

如果您使用的是 Tailwind CSS,您可以使用 @headlessui/tailwindcss 插件使用修饰符(例如 ui-open:*)定位此属性。

<template> <Popover> <PopoverButton> Solutions
<ChevronDownIcon class="ui-open:rotate-180 ui-open:transform" />
</PopoverButton> <PopoverPanel> <a href="/insights">Insights</a> <a href="/automations">Automations</a> <a href="/reports">Reports</a> </PopoverPanel> </Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' import { ChevronDownIcon } from '@heroicons/vue/20/solid' </script>

默认情况下,PopoverPanel 将根据 Popover 组件本身内部跟踪的打开状态自动显示/隐藏。

<template> <Popover> <PopoverButton>Solutions</PopoverButton> <!-- By default, the `PopoverPanel` will automatically show/hide when the `PopoverButton` is pressed. --> <PopoverPanel> <!-- ... --> </PopoverPanel> </Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' </script>

如果您希望自己处理此操作(也许是因为出于某种原因需要添加额外的包装元素),您可以将 static 属性传递给 PopoverPanel 以告诉它始终渲染,然后使用 open 插槽属性来控制何时自己显示/隐藏面板。

<template>
<Popover v-slot="{ open }">
<PopoverButton>Solutions</PopoverButton> <div v-if="open">
<!--
Using the `static` prop, the `PopoverPanel` is always
rendered and the `open` state is ignored.
--> <PopoverPanel static> <!-- ... --> </PopoverPanel> </div> </Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' </script>

由于弹出框可以包含交互式内容(例如表单控件),因此我们不能像在 Menu 组件中那样,当您单击其中的某个内容时自动关闭弹出框。

要在单击面板的子元素时手动关闭弹出框,请将该子元素渲染为 PopoverButton。您可以使用 :as 属性来自定义正在渲染的元素。

<template> <Popover> <PopoverButton>Solutions</PopoverButton> <PopoverPanel>
<PopoverButton :as="MyLink" href="/insights">Insights</PopoverButton>
<!-- ... --> </PopoverPanel> </Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' import MyLink from './MyLink' </script>

或者,PopoverPopoverPanel 公开了一个 close() 插槽属性,您可以使用它来强制关闭面板,例如在运行异步操作后。

<template> <Popover> <PopoverButton>Solutions</PopoverButton>
<PopoverPanel v-slot="{ close }">
<button @click="accept(close)">Read and accept</button>
</PopoverPanel>
</Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
async function accept(close) {
await fetch('/accept-terms', { method: 'POST' })
close()
}
</script>

默认情况下,PopoverButton 在调用 close() 后会获得焦点,但您可以通过将 ref 传递到 close(ref) 来更改此行为。

如果您希望在每次打开弹出框时在应用程序 UI 上方样式化一个背景,请使用 PopoverOverlay 组件。

<template> <Popover v-slot="{ open }"> <PopoverButton>Solutions</PopoverButton>
<PopoverOverlay class="fixed inset-0 bg-black opacity-30" />
<PopoverPanel> <!-- ... --> </PopoverPanel> </Popover> </template> <script setup> import { Popover, PopoverOverlay, PopoverButton, PopoverPanel, } from '@headlessui/vue' </script>

在此示例中,我们将 PopoverOverlay 放置在 DOM 中的 Panel 之前,这样它就不会覆盖面板的内容。

但与所有其他组件一样,PopoverOverlay 是完全无头的,因此如何样式化它由您决定。

要动画化弹出框面板的打开/关闭,可以使用 Vue 的内置 <transition> 元素。您只需将 PopoverPanel 包含在 <transition> 中,过渡就会自动应用。

<template> <Popover> <PopoverButton>Solutions</PopoverButton> <!-- Use the built-in `transition` component to add transitions. -->
<transition
enter-active-class="transition duration-200 ease-out"
enter-from-class="translate-y-1 opacity-0"
enter-to-class="translate-y-0 opacity-100"
leave-active-class="transition duration-150 ease-in"
leave-from-class="translate-y-0 opacity-100"
leave-to-class="translate-y-1 opacity-0"
>
<PopoverPanel> <!-- ... --> </PopoverPanel> </transition> </Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' </script>

如果您希望为弹出框的不同子元素协调多个过渡,请查看 无头 UI 中包含的过渡组件

在渲染多个相关的弹出框时,例如在网站的标题导航中,请使用 PopoverGroup 组件。这将确保面板在用户在组内的弹出框之间进行 Tab 键操作时保持打开状态,但一旦用户在组外进行 Tab 键操作,就会关闭任何打开的面板。

<template>
<PopoverGroup>
<Popover> <PopoverButton>Product</PopoverButton> <PopoverPanel> <!-- ... --> </PopoverPanel> </Popover> <Popover> <PopoverButton>Solutions</PopoverButton> <PopoverPanel> <!-- ... --> </PopoverPanel> </Popover>
</PopoverGroup>
</template> <script setup> import { PopoverGroup, Popover, PopoverButton, PopoverPanel, } from '@headlessui/vue' </script>

Popover 及其子组件分别渲染一个对该组件来说明智的默认元素:PopoverOverlayPanelGroup 组件都渲染一个 <div>,而 Button 组件则渲染一个 <button>

这很容易使用 as 属性更改,该属性存在于每个组件中。

<template> <!-- Render a `nav` instead of a `div` -->
<Popover as="nav">
<PopoverButton>Solutions</PopoverButton> <!-- Render a `form` instead of a `div` -->
<PopoverPanel as="form"><!-- ... --></PopoverPanel>
</Popover> </template> <script setup> import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' </script>

在打开的面板上按 Tab 键将使面板内容中的第一个可聚焦元素获得焦点。如果正在使用 PopoverGroup,Tab 键将从打开的面板内容的末尾循环到下一个弹出框的按钮。

单击 PopoverButton 将切换面板打开和关闭。单击打开面板以外的任何位置将关闭该面板。

命令描述

EnterSpacePopoverButton 获得焦点时。

切换面板

Esc

关闭任何打开的弹出框

Tab

在打开的面板内容中循环

从打开的面板中 Tab 键退出将关闭该面板,并且从一个打开的面板到同级弹出框的按钮(在 PopoverGroup 内)的 Tab 键操作将关闭第一个面板。

Shift + Tab

在焦点顺序中向后循环

支持嵌套弹出框,并且所有面板将在根面板关闭时正确关闭。

所有相关的 ARIA 属性都会自动管理。

以下是弹出框与其他类似组件的比较方式

  • <Menu />. 弹出框比菜单更通用。菜单只支持非常有限的内容,并且具有特定的无障碍语义。箭头键还会导航菜单的项目。菜单最适合 UI 元素,这些元素类似于在大多数操作系统标题栏中找到的菜单。如果您的浮动面板包含图像或比简单链接更多的标记,请使用弹出框。

  • <Disclosure />. 公开用于通常会重新流文档的内容,例如手风琴。弹出框还在公开之上具有额外的行为:它们会渲染覆盖层,并且当用户单击覆盖层(通过单击弹出框内容之外)或按 Esc 键时会关闭。如果您的 UI 元素需要此行为,请使用弹出框而不是公开。

  • <Dialog />. 对话框旨在完全吸引用户的注意力。它们通常在屏幕中央渲染一个浮动面板,并使用背景来使应用程序内容的其余部分变暗。它们还会捕获焦点,并防止在对话框被关闭之前从对话框内容中 Tab 键退出。弹出框更具上下文相关性,通常定位在触发它们的元素附近。

主弹出框组件。

属性默认值描述
asdiv
字符串 | 组件

Popover 应该渲染成的元素或组件。

插槽属性描述
open

布尔值

弹出框是打开还是关闭。

close

(ref?: ref | HTMLElement) => void

关闭弹出框并重新聚焦 PopoverButton。可以选择传入一个 refHTMLElement 来代替聚焦该元素。

这可用于为你的 Popover 组件创建一个覆盖层。点击覆盖层将关闭 Popover。

属性默认值描述
asdiv
字符串 | 组件

PopoverOverlay 应该渲染的元素或组件。

插槽属性描述
open

布尔值

弹出框是打开还是关闭。

这是用于切换 Popover 的触发组件。你也可以在 PopoverPanel 内使用 PopoverButton 组件,如果这样做,它将充当 关闭 按钮。我们也会确保在按钮上提供正确的 aria-* 属性。

属性默认值描述
as按钮
字符串 | 组件

PopoverButton 应该渲染的元素或组件。

插槽属性描述
open

布尔值

弹出框是打开还是关闭。

此组件包含你 Popover 的内容。

属性默认值描述
asdiv
字符串 | 组件

PopoverPanel 应该渲染的元素或组件。

焦点false
布尔值

这将在 Popover 打开时强制在 PopoverPanel 内部获得焦点。如果焦点离开此组件,它也会关闭 Popover

静态false
布尔值

元素是否应该忽略内部管理的打开/关闭状态。

注意:staticunmount 不能同时使用。如果你尝试这样做,你会得到一个 TypeScript 错误。

卸载true
布尔值

元素是否应该根据打开/关闭状态进行卸载或隐藏。

注意:staticunmount 不能同时使用。如果你尝试这样做,你会得到一个 TypeScript 错误。

插槽属性描述
open

布尔值

弹出框是打开还是关闭。

close

(ref?: ref | HTMLElement) => void

关闭弹出框并重新聚焦 PopoverButton。可以选择传入一个 refHTMLElement 来代替聚焦该元素。

通过将相关联的兄弟 Popover 包含在 PopoverGroup 中来链接它们。从一个 PopoverPanel 中跳出将使下一个 Popover 的 PopoverButton 获得焦点,而从 PopoverGroup 中跳出将完全关闭组内的所有 Popover。

属性默认值描述
asdiv
字符串 | 组件

PopoverGroup 应该渲染的元素或组件。

如果你对使用 Headless UI 和 Tailwind CSS 的预先设计的组件示例感兴趣,请查看 Tailwind UI - 一个由我们精心打造的精美设计和精心制作的组件集合。

它是支持我们开源项目工作的一个好方法,它使我们能够改进它们并保持良好的维护。