浮動視窗

浮動視窗非常適合用於固定在頁面上的面板,裡面可以顯示各種內容,例如導覽功能表、行動裝置功能表,或是彈出式功能表。

首先,透過 npm 安裝 Headless 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 附近,而且不會影響正常的文件流動。

Headless 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` 時,它們將在此屬性中列為以空格分隔的字串,以便你可以使用格式為 `[attr~=value]` 的 CSS 屬性選擇器 來鎖定它們。

例如,以下是 `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>

此外,`Popover` 和 `PopoverPanel` 公開了 `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()` 後接收焦點,但你可以透過傳遞參照到 `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> 元素。您只需使用 <transition> 包裝 PopoverPanel,即可自動套用過渡效果。

<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 及其子元件各自會針對該元件呈現合適的預設元素:PopoverOverlayPanel 和 Group 元件全部會呈現 <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 出來會關閉該面板,從一個開啟面板 Tab 到同組提示視窗的按鈕(在提示視窗群組中)會關閉第一個面板

Shift + Tab

反向循環瀏覽焦點順序

支援巢狀提示視窗,所有面板都將在根面板關閉時正確關閉。

所有相關的 ARIA 屬性均自動管理。

提示視窗與其他類似組件相比如下

  • <Menu />. 提示視窗比選單更具一般用途。選單僅支援非常受限的內容,並具有特定的無障礙語義。箭頭鍵也會導覽選單的項目。選單最適合用於類似於您在多數作業系統標題列中找到的選單等使用者介面元素。如果您的浮動面板包含圖像或比單純連結更多的標記,請使用提示視窗。

  • <Disclosure />. 揭露適用於通常會重排文件的內容,例如手風琴。提示視窗在揭露的基礎上還有額外的行為:它們會呈現覆蓋層,並且在使用者點按覆蓋層(點按在提示視窗內容之外)或按下跳脫鍵時關閉。如果您的使用者介面元素需要此行為,請使用提示視窗而不是揭露。

  • <Dialog />. 對話框旨在吸引使用者的全部注意力。它們通常會在螢幕中央呈現浮動面板,並使用背景淡化應用程式內容的其餘部分。它們還會擷取焦點,並防止從對話框的內容 Tab 到其他地方,直到對話框被取消。提示視窗更具情境性,通常會置於觸發它们的元素附近。

主要的浮動提示元件。

Prop預設值描述
asdiv
字串 | 元件

Popover 應該要呈現為的元素或元件。

Slot Prop描述
open

布林值

浮動提示是否開啟。

close

(ref?: ref | HTMLElement) => void

關閉浮動提示並將焦點移回 PopoverButton。可選擇傳入 refHTMLElement 來將焦點移往該元素。

這可用來為浮動提示元件建立覆蓋圖層。點選覆蓋圖層會關閉浮動提示。

Prop預設值描述
asdiv
字串 | 元件

PopoverOverlay 應該要呈現為的元素或元件。

Slot Prop描述
open

布林值

浮動提示是否開啟。

這是用來切換浮動提示的觸發器元件。您也可以在 PopoverPanel 內使用此 PopoverButton 元件,這樣的話它就會作為 close 按鈕。我們也會確保在按鈕上提供正確的 aria-* 屬性。

Prop預設值描述
asbutton
字串 | 元件

PopoverButton 應該要呈現為的元素或元件。

Slot Prop描述
open

布林值

浮動提示是否開啟。

這個元件包含浮動提示的內容。

Prop預設值描述
asdiv
字串 | 元件

PopoverPanel 應該要呈現為的元素或元件。

focusfalse
布林值

Popover 開啟時,這將會強制 PopoverPanel 內聚焦。如果焦點離開此元件,這也會關閉 Popover

staticfalse
布林值

元素是否應忽略內部管理的開啟/關閉狀態。

注意:staticunmount 不能同時使用。如果您嘗試這樣做,您會收到 TypeScript 錯誤。

unmounttrue
布林值

元素是否應根據開啟/關閉狀態解除掛載或隱藏。

注意:staticunmount 不能同時使用。如果您嘗試這樣做,您會收到 TypeScript 錯誤。

Slot Prop描述
open

布林值

浮動提示是否開啟。

close

(ref?: ref | HTMLElement) => void

關閉浮動提示並將焦點移回 PopoverButton。可選擇傳入 refHTMLElement 來將焦點移往該元素。

將相關的同層浮動提示包覆在 PopoverGroup 中以產生連結。從一個 PopoverPanel 跳出將會將焦點移至下一個浮動提示的 PopoverButton,而從 PopoverGroup 完全跳出將會關閉群組中的所有浮動提示。

Prop預設值描述
asdiv
字串 | 元件

PopoverGroup 應該要呈現為的元素或元件。

如果您有興趣查看使用 Headless UI 和 Tailwind CSS 的預先設計元件範例,請查看Tailwind UI—我們建立的精緻設計且製作精良的元件集合。

這是支援我們開發如本項目的開源專案的絕佳方式,並讓我們得以改進專案並使其維持良好。