Popover

ポップオーバーは、ナビゲーションメニュー、モバイルメニュー、フライアウトメニューのような、任意のコンテンツを含むフローティングパネルに最適です。

まず、npm を使用して Headless UI をインストールします。

npm install @headlessui/react

ポップオーバーは、PopoverPopover.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 の近くに配置し、通常のドキュメントフローを妨げないようにしています。

Headless 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 の場合、それらはスペース区切りの文字列としてこの属性にリストされるため、[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:* のような修飾子でこの属性をターゲットにすることができます。

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> ) }

ポップオーバーが実際にボタンの近くにフローティングパネルをレンダリングするには、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.Panel
ref={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> ) }

(何らかの理由で追加のラッパー要素を追加する必要があるなどの理由で) これを自分で処理する場合は、Popover.Panelstatic プロップを渡して常にレンダリングするように指示し、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 always
rendered 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.Buttonclose() の呼び出し後にフォーカスを受け取りますが、close(ref) に 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 を DOM 内の Panel の前に配置して、パネルの内容を覆い隠さないようにしています。

しかし、他のすべてのコンポーネントと同様に、Popover.Overlay は完全にヘッドレスであるため、どのようにスタイルを設定するかはあなた次第です。

ポップオーバーパネルの開閉をアニメーション化するには、提供されている Transition コンポーネントを使用します。必要なのは、Popover.Panel<Transition> でラップするだけで、トランジションが自動的に適用されます。

import { Popover, Transition } from '@headlessui/react' function MyPopover() { return ( <Popover> <Popover.Button>Solutions</Popover.Button>
<Transition
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"
>
<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. */} <Transition
show={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>
)
}

レンダーレスであるため、Headless UI コンポーネントは、Framer MotionReact Spring のような React エコシステムの他のアニメーションライブラリともうまく構成されます。

サイトのヘッダーナビゲーションのように、いくつかの関連するポップオーバーをレンダリングする場合は、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 とそのサブコンポーネントはそれぞれ、そのコンポーネントに適したデフォルト要素をレンダリングします。PopoverOverlayPanel、および Group コンポーネントはすべて <div> をレンダリングし、Button コンポーネントは <button> をレンダリングします。

コンポーネントを異なる要素として、または独自のカスタムコンポーネントとしてレンダリングするには、as プロップを使用します。カスタムコンポーネントが、Headless UI が適切に接続できるように ref を転送するようにしてください。

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 または SpacePopover.Button にフォーカスがあるとき。

パネルの切り替え

Esc

開いているすべての Popover を閉じます

Tab

開いているパネルの内容を順番に移動します

開いているパネルからタブアウトすると、そのパネルが閉じられます。また、開いているパネルから兄弟 Popover のボタン (Popover.Group 内) にタブ移動すると、最初のパネルが閉じられます

Shift + Tab

フォーカス順を逆方向に移動します

ネストされた Popover がサポートされており、ルートパネルが閉じられると、すべてのパネルが正しく閉じられます。

関連するすべての ARIA 属性が自動的に管理されます。

Popover と他の類似コンポーネントの比較は以下のとおりです

  • <Menu />。Popover は Menu より汎用性が高いです。Menu は非常に限られたコンテンツのみをサポートし、特定のアクセシビリティセマンティクスを持っています。また、矢印キーで Menu の項目をナビゲートします。Menu は、ほとんどのオペレーティングシステムのタイトルバーにあるメニューのような UI 要素に最適です。フローティングパネルに画像や単純なリンク以上のマークアップが含まれている場合は、Popover を使用してください。

  • <Disclosure />。Disclosure は、通常、アコーディオンのようにドキュメントをリフローするようなものに便利です。Popover は、Disclosure に加えて、オーバーレイをレンダリングし、ユーザーがオーバーレイをクリックした(Popover のコンテンツの外側をクリックした)か、エスケープキーを押したときに閉じられるという追加の動作があります。UI 要素にこの動作が必要な場合は、Disclosure の代わりに Popover を使用してください。

  • <Dialog />。Dialog は、ユーザーの注意を完全に引きつけるためのものです。通常、画面の中央にフローティングパネルをレンダリングし、アプリケーションの残りのコンテンツを暗くするために背景を使用します。また、フォーカスをキャプチャし、Dialog が閉じられるまで Dialog のコンテンツからタブ移動できないようにします。Popover はよりコンテキスト依存であり、通常はトリガーとなった要素の近くに配置されます。

メインの Popover コンポーネント。

Propデフォルト説明
asdiv
String | Component

Popover がレンダリングする要素またはコンポーネント。

レンダープロップ説明
open

Boolean

ポップオーバーが開いているかどうか。

close

(ref?: ref | HTMLElement) => void

ポップオーバーを閉じ、Popover.Button にフォーカスを戻します。オプションで、代わりにフォーカスを当てたい要素の **ref** または **HTMLElement** を渡すことができます。

これは、Popover コンポーネントのオーバーレイを作成するために使用できます。オーバーレイをクリックすると、Popover が閉じます。

Propデフォルト説明
asdiv
String | Component

Popover.Overlay がレンダリングする要素またはコンポーネント。

レンダープロップ説明
open

Boolean

ポップオーバーが開いているかどうか。

これは、Popover を切り替えるためのトリガーコンポーネントです。この Popover.Button コンポーネントは Popover.Panel 内でも使用できます。その場合、close ボタンとして動作します。また、ボタンに正しい aria-* 属性を設定します。

Propデフォルト説明
asbutton
String | Component

Popover.Button がレンダリングする要素またはコンポーネント。

レンダープロップ説明
open

Boolean

ポップオーバーが開いているかどうか。

このコンポーネントには、Popover の内容が含まれます。

Propデフォルト説明
asdiv
String | Component

Popover.Panel がレンダリングする要素またはコンポーネント。

focusfalse
Boolean

これは、Popover が開いているときに Popover.Panel 内に強制的にフォーカスを当てます。また、フォーカスがこのコンポーネントから離れた場合、Popover を閉じます。

staticfalse
Boolean

要素が内部で管理されている開閉状態を無視するかどうか。

注: staticunmount は同時に使用できません。試すと TypeScript エラーが発生します。

unmounttrue
Boolean

要素を開閉状態に基づいてアンマウントするか、非表示にするか。

注: staticunmount は同時に使用できません。試すと TypeScript エラーが発生します。

レンダープロップ説明
open

Boolean

ポップオーバーが開いているかどうか。

close

(ref?: ref | HTMLElement) => void

ポップオーバーを閉じ、Popover.Button にフォーカスを戻します。オプションで、代わりにフォーカスを当てたい要素の **ref** または **HTMLElement** を渡すことができます。

関連する兄弟ポップオーバーを Popover.Group でラップしてリンクします。ある Popover.Panel からタブアウトすると、次のポップオーバーの Popover.Button にフォーカスが移動し、Popover.Group の外側に完全にタブ移動すると、グループ内のすべてのポップオーバーが閉じられます。

Propデフォルト説明
asdiv
String | Component

Popover.Group がレンダリングする要素またはコンポーネント。

Headless UI と Tailwind CSS を使用した、事前設計されたコンポーネントの例に興味がある場合は、私たちによって美しくデザインされ、専門的に作成されたコンポーネントのコレクションである Tailwind UI を確認してください。

これは、このようなオープンソースプロジェクトでの私たちの活動をサポートする素晴らしい方法であり、それらを改善し、適切にメンテナンスし続けることを可能にします。