Popover
弹出窗口非常适合浮动面板,其中包含任意内容,例如导航菜单、移动菜单和弹出菜单。
要开始使用,请通过 npm 安装无状态 UI
npm install @headlessui/react
弹出窗口使用 Popover
、Popover.Button
和 Popover.Panel
组件构建。
点击 Popover.Button
将自动打开/关闭 Popover.Panel
。当面板打开时,点击其内容外部的任何位置、按 Esc 键或跳出面板将关闭弹出窗口。
import { Popover } from '@headlessui/react' function MyPopover() { return ( <Popover className="relative"> <Popover.Button>Solutions</Popover.Button> <Popover.Panel className="absolute z-10"> <div className="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="" /> </Popover.Panel> </Popover> ) }
这些组件完全没有样式,因此如何对 Popover
进行样式化由你决定。在我们的示例中,我们在 Popover.Panel
上使用绝对定位来将其定位在 Popover.Button
附近,而不会干扰正常的文档流。
无状态 UI 会跟踪每个组件的许多状态信息,例如当前选中的哪个列表框选项、弹出窗口是打开还是关闭,或者当前通过键盘激活的弹出窗口中的哪个项目。
但由于这些组件是无状态的,并且在开箱即用时完全没有样式,因此你无法看到这些信息在你的 UI 中,除非你自己提供每个状态所需的样式。
每个组件都会通过你用来有条件地应用不同样式或渲染不同内容的渲染道具来公开其当前状态的信息。
例如,Popover
组件公开了一个 open
状态,它告诉你弹出窗口当前是否打开。
import { Popover } from '@headlessui/react' import { ChevronDownIcon } from '@heroicons/react/20/solid' function MyPopover() { return ( <Popover>
{({ open }) => (/* Use the `open` state to conditionally change the direction of the chevron icon. */ <> <Popover.Button> Solutions <ChevronDownIcon className={open ? 'rotate-180 transform' : ''} /> </Popover.Button><Popover.Panel><a href="/insights">Insights</a> <a href="/automations">Automations</a> <a href="/reports">Reports</a> </Popover.Panel> </> )} </Popover> ) }
有关每个组件的完整渲染道具 API,请参阅组件 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:*
)定位此属性
import { Popover } from '@headlessui/react' import { ChevronDownIcon } from '@heroicons/react/20/solid' function MyPopover() { return ( <Popover> <Popover.Button> Solutions
<ChevronDownIcon className="ui-open:rotate-180 ui-open:transform" /></Popover.Button> <Popover.Panel> <a href="/insights">Insights</a> <a href="/automations">Automations</a> <a href="/reports">Reports</a> </Popover.Panel> </Popover> ) }
要使 Popover 实际渲染一个浮动面板,使其靠近你的按钮,你需要使用一些依赖于 CSS、JS 或两者的样式技术。在前面的示例中,我们使用了 CSS 绝对和相对定位,以便面板渲染在打开它的按钮附近。
对于更复杂的方法,你可能可以使用像Popper JS这样的库。这里我们使用 Popper 的 usePopper
hook 来将 Popover.Panel
渲染为一个浮动面板,使其靠近按钮。
import { useState } from 'react' import { Popover } from '@headlessui/react' import { usePopper } from 'react-popper' function MyPopover() {
let [referenceElement, setReferenceElement] = useState()let [popperElement, setPopperElement] = useState()let { styles, attributes } = usePopper(referenceElement, popperElement)return ( <Popover><Popover.Button ref={setReferenceElement}>Solutions</Popover.Button><Popover.Panelref={setPopperElement}style={styles.popper}{...attributes.popper}> {/* ... */} </Popover.Panel> </Popover> ) }
默认情况下,你的 Popover.Panel
会根据 Popover
组件本身内部跟踪的内部打开状态自动显示/隐藏。
import { Popover } from '@headlessui/react' function MyPopover() { return ( <Popover> <Popover.Button>Solutions</Popover.Button> {/* By default, the `Popover.Panel` will automatically show/hide when the `Popover.Button` is pressed. */} <Popover.Panel>{/* ... */}</Popover.Panel> </Popover> ) }
如果你希望自己处理(也许是因为你出于某种原因需要添加一个额外的包装器元素),你可以将 static
属性传递给 Popover.Panel
以告诉它始终渲染,然后使用 open
渲染道具来自己控制何时显示/隐藏面板。
import { Popover } from '@headlessui/react' function MyPopover() { return ( <Popover> {({ open }) => ( <> <Popover.Button>Solutions</Popover.Button>
{open && (<div>{/*Using the `static` prop, the `Popover.Panel` is alwaysrendered and the `open` state is ignored.*/}<Popover.Panel static>{/* ... */}</Popover.Panel></div>)}</> )} </Popover> ) }
由于弹出窗口可能包含交互式内容(如表单控件),因此我们无法像 Menu
组件那样,在你点击其内部的某些内容时自动关闭它们。
要在点击面板的子项时手动关闭弹出窗口,请将该子项渲染为 Popover.Button
。你可以使用 as
属性来自定义要渲染的元素。
import { Popover } from '@headlessui/react' import MyLink from './MyLink' function MyPopover() { return ( <Popover> <Popover.Button>Solutions</Popover.Button> <Popover.Panel>
<Popover.Button as={MyLink} href="/insights">Insights</Popover.Button>{/* ... */} </Popover.Panel> </Popover> ) }
或者,Popover
和 Popover.Panel
公开了一个 close()
渲染道具,你可以使用它来强制关闭面板,例如在运行异步操作之后
import { Popover } from '@headlessui/react' function MyPopover() { return ( <Popover> <Popover.Button>Terms</Popover.Button> <Popover.Panel>
{({ close }) => (<button onClick={async () => {await fetch('/accept-terms', { method: 'POST' })close()}} > Read and accept </button> )} </Popover.Panel> </Popover> ) }
默认情况下,Popover.Button
会在调用 close()
后接收焦点,但你可以通过将 ref 传递给 close(ref)
来更改此行为。
如果你希望在每次打开弹出窗口时在应用程序 UI 上添加一个背景,请使用 Popover.Overlay
组件
import { Popover } from '@headlessui/react' function MyPopover() { return ( <Popover> {({ open }) => ( <> <Popover.Button>Solutions</Popover.Button>
<Popover.Overlay className="fixed inset-0 bg-black opacity-30" /><Popover.Panel>{/* ... */}</Popover.Panel> </> )} </Popover> ) }
在这个示例中,我们在 DOM 中将 Popover.Overlay
放置在 Panel
之前,这样它就不会覆盖面板的内容。
但与所有其他组件一样,Popover.Overlay
是完全无状态的,因此如何对其进行样式化由你决定。
要为弹出窗口面板的打开/关闭添加动画,请使用提供的 Transition
组件。你只需将 Popover.Panel
包裹在 <Transition>
中,过渡就会自动应用。
import { Popover, Transition } from '@headlessui/react' function MyPopover() { return ( <Popover> <Popover.Button>Solutions</Popover.Button>
<Transitionenter="transition duration-100 ease-out"enterFrom="transform scale-95 opacity-0"enterTo="transform scale-100 opacity-100"leave="transition duration-75 ease-out"leaveFrom="transform scale-100 opacity-100"leaveTo="transform scale-95 opacity-0"><Popover.Panel>{/* ... */}</Popover.Panel></Transition></Popover> ) }
默认情况下,我们内置的 Transition
组件会自动与 Popover
组件通信,以处理打开/关闭状态。但是,如果你需要更多地控制此行为,你可以显式地控制它
import { Popover, Transition } from '@headlessui/react' function MyPopover() { return ( <Popover>
{({ open }) => (<><Popover.Button>Solutions</Popover.Button> {/* Use the `Transition` component. */} <Transitionshow={open}enter="transition duration-100 ease-out" enterFrom="transform scale-95 opacity-0" enterTo="transform scale-100 opacity-100" leave="transition duration-75 ease-out" leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" > {/* Mark this component as `static` */}<Popover.Panel static>{/* ... */}</Popover.Panel></Transition> </> )}</Popover>)}
由于它们是无状态的,因此无状态 UI 组件也可以很好地与 React 生态系统中的其他动画库(如Framer Motion 和React Spring)组合。
在渲染多个相关的弹出窗口时(例如,在站点的页眉导航中),请使用 Popover.Group
组件。这将确保在用户在组内的弹出窗口之间进行制表时面板保持打开状态,但在用户制表到组外部时会关闭任何打开的面板
import { Popover } from '@headlessui/react' function MyPopover() { return (
<Popover.Group><Popover> <Popover.Button>Product</Popover.Button> <Popover.Panel>{/* ... */}</Popover.Panel> </Popover> <Popover> <Popover.Button>Solutions</Popover.Button> <Popover.Panel>{/* ... */}</Popover.Panel> </Popover></Popover.Group>) }
Popover
及其子组件都渲染一个适合该组件的默认元素:Popover
、Overlay
、Panel
和 Group
组件都渲染一个 <div>
,Button
组件渲染一个 <button>
。
使用 as
属性将组件渲染为不同的元素或作为你自己的自定义组件,确保你的自定义组件转发 refs,以便无状态 UI 可以正确地进行连接。
import { forwardRef } from 'react' import { Popover } from '@headlessui/react'
let MyCustomButton = forwardRef(function (props, ref) {return <button className="..." ref={ref} {...props} />}) function MyPopover() {return (<Popover as="nav"><Popover.Button as={MyCustomButton}> Solutions </Popover.Button><Popover.Panel as="form"> {/* ... */} </Popover.Panel> </Popover> ) }
在打开的面板上按 Tab 键将在面板内容中的第一个可聚焦元素上设置焦点。如果正在使用 Popover.Group
,则 Tab 键会在打开面板内容的末尾和下一个弹出窗口的按钮之间循环。
点击 Popover.Button
将切换面板的打开和关闭状态。点击打开面板外部的任何位置将关闭该面板。
命令 | 描述 |
Enter 或 Space当 | 切换面板 |
Esc | 关闭任何打开的弹出窗口 |
Tab | 在打开面板的内容之间循环 从打开的面板中跳出将关闭该面板,并且从一个打开的面板跳到兄弟弹出窗口的按钮(在 Popover.Group 中)将关闭第一个面板 |
Shift + Tab | 在焦点顺序中向后循环 |
支持嵌套弹出窗口,并且所有面板都将在根面板关闭时正确关闭。
所有相关的 ARIA 属性都会自动管理。
以下是弹出窗口与其他类似组件的比较
-
<Menu />
。Popover 比菜单用途更广泛。菜单仅支持非常有限的内容,并且具有特定的可访问性语义。箭头键也用于导航菜单的项目。菜单最适合类似于大多数操作系统标题栏中找到的菜单的 UI 元素。如果您的浮动面板包含图像或比简单链接更多的标记,请使用 Popover。 -
<Disclosure />
。Disclosures 适用于通常重新排列文档的内容,例如手风琴。Popover 还具有 Disclosure 之上的额外行为:它们呈现覆盖层,并且在用户单击覆盖层(通过单击 Popover 内容之外)或按下 Escape 键时关闭。如果您的 UI 元素需要此行为,请使用 Popover 而不是 Disclosure。 -
<Dialog />
。Dialogs 旨在吸引用户的全部注意力。它们通常在屏幕中央呈现浮动面板,并使用背景来使应用程序内容的其余部分变暗。它们还会捕获焦点,并阻止用户将标签键移出 Dialog 内容,直到 Dialog 被关闭。Popover 更具上下文性,通常位于触发它们的元素附近。
主要的 Popover 组件。
属性 | 默认值 | 描述 |
as | div | String | Component
|
渲染属性 | 描述 |
open |
Popover 是否打开。 |
close |
关闭 popover 并重新聚焦 |
属性 | 默认值 | 描述 |
as | div | String | Component
|
渲染属性 | 描述 |
open |
Popover 是否打开。 |
这是用于切换 Popover 的触发组件。您也可以将此 Popover.Button
组件用在 Popover.Panel
中,如果您这样做,它将作为 close
按钮。我们还将确保为按钮提供正确的 aria-*
属性。
属性 | 默认值 | 描述 |
as | button | String | Component
|
渲染属性 | 描述 |
open |
Popover 是否打开。 |
属性 | 默认值 | 描述 |
as | div | String | Component
|
focus | false | Boolean 这将在 |
static | false | Boolean 元素是否应忽略内部管理的打开/关闭状态。 注意: |
unmount | true | Boolean 元素是否应根据打开/关闭状态卸载或隐藏。 注意: |
渲染属性 | 描述 |
open |
Popover 是否打开。 |
close |
关闭 popover 并重新聚焦 |
通过将相关联的兄弟 popover 包裹在 Popover.Group
中来链接它们。从一个 Popover.Panel
中跳出将聚焦下一个 popover 的 Popover.Button
,而跳出 Popover.Group
将完全关闭组中的所有 popover。
属性 | 默认值 | 描述 |
as | div | String | Component
|