Popover
ポップオーバーは、ナビゲーションメニュー、モバイルメニュー、フライアウトメニューのような、任意のコンテンツを含むフローティングパネルに最適です。
まず、npm を使用して Headless 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
の近くに配置し、通常のドキュメントフローを妨げないようにしています。
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.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> ) }
(何らかの理由で追加のラッパー要素を追加する必要があるなどの理由で) これを自分で処理する場合は、Popover.Panel
に static
プロップを渡して常にレンダリングするように指示し、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()
の呼び出し後にフォーカスを受け取りますが、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>
<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>)}
レンダーレスであるため、Headless UI コンポーネントは、Framer Motion や React 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
とそのサブコンポーネントはそれぞれ、そのコンポーネントに適したデフォルト要素をレンダリングします。Popover
、Overlay
、Panel
、および 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 または Space | パネルの切り替え |
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 | デフォルト | 説明 |
as | div | String | Component
|
レンダープロップ | 説明 |
open |
ポップオーバーが開いているかどうか。 |
close |
ポップオーバーを閉じ、 |
Prop | デフォルト | 説明 |
as | div | String | Component
|
レンダープロップ | 説明 |
open |
ポップオーバーが開いているかどうか。 |
これは、Popover を切り替えるためのトリガーコンポーネントです。この Popover.Button
コンポーネントは Popover.Panel
内でも使用できます。その場合、close
ボタンとして動作します。また、ボタンに正しい aria-*
属性を設定します。
Prop | デフォルト | 説明 |
as | button | String | Component
|
レンダープロップ | 説明 |
open |
ポップオーバーが開いているかどうか。 |
Prop | デフォルト | 説明 |
as | div | String | Component
|
focus | false | Boolean これは、 |
static | false | Boolean 要素が内部で管理されている開閉状態を無視するかどうか。 注: |
unmount | true | Boolean 要素を開閉状態に基づいてアンマウントするか、非表示にするか。 注: |
レンダープロップ | 説明 |
open |
ポップオーバーが開いているかどうか。 |
close |
ポップオーバーを閉じ、 |
関連する兄弟ポップオーバーを Popover.Group
でラップしてリンクします。ある Popover.Panel
からタブアウトすると、次のポップオーバーの Popover.Button
にフォーカスが移動し、Popover.Group
の外側に完全にタブ移動すると、グループ内のすべてのポップオーバーが閉じられます。
Prop | デフォルト | 説明 |
as | div | String | Component
|