彈出式視窗
彈出式視窗非常適合用於包含任意內容的浮動面板,例如導覽選單、行動裝置選單和飛出式選單。
首先,透過 npm 安裝 Headless UI
npm install @headlessui/react
彈出式視窗使用 Popover
、Popover.Button
和 Popover.Panel
元件建置。
按一下 Popover.Button
將自動開啟/關閉 Popover.Panel
。開啟面板時,按一下其內容以外的任何位置、按一下 ESC 鍵或從面板中以 Tab 鍵切換離開,即可關閉彈出式視窗。
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.Panel
上使用絕對定位,將其定位在 Popover.Button
附近,並不會影響一般的文件流程。
Headless UI 會追蹤各個元件的許多狀態,例如目前選取哪個清單框選項、彈出式視窗是否開啟或關閉,或彈出式視窗中的哪個項目目前已透過鍵盤啟用。
但是,由於元件出廠設定並未套用樣式,因此在您自己提供每種狀態所需的樣式之前,您無法在 UI 中看見這些資訊。
每個元件都會透過 渲染屬性公開其目前狀態的資訊,您可以使用這些屬性有條件地套用不同樣式或渲染不同內容。
例如,Popover
元件顯示 open
狀態,在這個狀態中會告訴您 popover 目前是否開啟。
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> ) }
如需每項元件的完整渲染 prop API,請參考 元件 API 文件。
每個元件也會透過 data-headlessui-state
屬性公開其目前狀態的相關資訊,您可以使用該屬性有條件地套用不同樣式。
當 渲染 prop API 中的任何狀態為 true
時,這些狀態會在此屬性中列為以空白分隔的字串,這樣您就可以使用 [attr~=value]
形式的 CSS 屬性選擇器 將其設為目標。
例如,以下是 popover 開啟時 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
勾子將我們的 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
prop 傳遞給 Popover.Panel
以指示它始終渲染,然後使用 open
渲染 prop 來控制面板的顯示/隱藏時間。
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> ) }
預設情況下,呼叫 close()
後,Popover.Button
會接收焦點,但你可以透過將參照傳入 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> ) }
在此範例中,我們將 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
組件。這能確保當使用者在群組內的不同浮動視窗之間以 Tab 鍵移動時,浮動視窗會保持開啟,但當使用者在群組外以 Tab 鍵移動時,所有開啟的浮動視窗都會關閉
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
屬性,將組件顯示為不同的元素或您自己的自訂組件,確定您的自訂組件 forward refs,以確保 Headless 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 | 循環於開啟的面板內容之間 從開啟的面板按下 Tab 鍵離開,將會關閉該面板,從一個開啟的面板按下 Tab 鍵,移動至同層對話框的按鈕(在 Popover.Group 內),會關閉第一個面板。 |
Shift + Tab | 向後循環焦點順序 |
支援嵌套對話框,且每當關閉根目錄面板時,所有面板都會正確關閉。
所有相關的 ARIA 屬性都會自動管理。
以下為對話框與其他類似組件的比較
-
<Menu />
。Popovers 的用途比功能單更廣泛。功能單僅支援非常受限的內容並具有特定無障礙語意。箭頭鍵也能在功能單的項目中移動。功能單最適合類似於大多數作業系統標題列中功能單的 UI 元件。如果您的浮動面板具有圖像或比單純連結更多的標記,請使用 Popover。 -
<Disclosure />
。揭露對於通常會重新整理文件的項目很有用,例如手風琴。Popovers 也具有揭露以外的行為:它們會呈現疊加,並且當使用者按一下疊加(按一下 Popover 內容以外的地方)或按一下跳脫鍵時關閉。如果您的 UI 元件需要這種行為,請使用 Popover 而非揭露。 -
<Dialog />
。對話框旨在吸引使用者的全部注意力。它們通常會在螢幕中央呈現一個浮動面板,並且透過幕景將應用程式其他內容變暗。它們也會擷取焦點,並防止從對話框內容中跳離,直到對話框被關閉為止。Popovers 的語境性更強,並且通常會定位在觸發它們的元件附近。
主要的 Popover 元件。
屬性 | 預設 | 說明 |
as | div | 字串 | 元件
|
Render 屬性 | 說明 |
open |
popover 是否開啟。 |
close |
關閉 popover 並將焦點重新設定為 |
屬性 | 預設 | 說明 |
as | div | 字串 | 元件
|
Render 屬性 | 說明 |
open |
popover 是否開啟。 |
這是切換 Popover 的觸發元件。您也可以在 Popover.Panel
內使用這個 Popover.Button
元件,如果您這樣做,它會表現得像 close
按鈕。我們還會確定在按鈕中提供正確的 aria-*
屬性。
屬性 | 預設 | 說明 |
as | button | 字串 | 元件
|
Render 屬性 | 說明 |
open |
popover 是否開啟。 |
屬性 | 預設 | 說明 |
as | div | 字串 | 元件
|
焦點 | false | 布林 當 |
靜態 | false | 布林 這個元素是否應忽略內部管理的開啟/關閉狀態。 注意: |
卸載 | true | 布林 這個元素是否應該根據開啟/關閉狀態進行卸載或隱藏。 注意: |
Render 屬性 | 說明 |
open |
popover 是否開啟。 |
close |
關閉 popover 並將焦點重新設定為 |
透過將相關兄弟元素的 popover 包在 Popover.Group
內進行連結。從一個 Popover.Panel
中按 Tab 離開會聚焦下一個 popover 的 Popover.Button
,而從 Popover.Group
外按 Tab 離開則會關閉群組中的所有 popover。
屬性 | 預設 | 說明 |
as | div | 字串 | 元件
|