如何使用 Headless UI 折叠兄弟姐妹

How to collapse Siblings with Headless UI

为了使用 Headless UI 制作手风琴组件,我使用了 Disclosure 组件。但是我有一个问题来控制它的兄弟姐妹的 collapse/expand 状态。

因此,我想在打开一个时关闭其他兄弟,但 Disclosure 组件仅支持内部渲染道具,打开和关闭。所以,我无法在组件外控制它,打开一个也无法关闭其他组件。

import { Disclosure } from '@headlessui/react'
import { ChevronUpIcon } from '@heroicons/react/solid'

export default function Example() {
  return (
    <div className="w-full px-4 pt-16">
      <div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2">
        <Disclosure>
          {({ open }) => (
            <>
              <Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
                <span>What is your refund policy?</span>
                <ChevronUpIcon
                  className={`${
                    open ? 'rotate-180 transform' : ''
                  } h-5 w-5 text-purple-500`}
                />
              </Disclosure.Button>
              <Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
                If you're unhappy with your purchase for any reason, email us
                within 90 days and we'll refund you in full, no questions asked.
              </Disclosure.Panel>
            </>
          )}
        </Disclosure>
        <Disclosure as="div" className="mt-2">
          {({ open }) => (
            <>
              <Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
                <span>Do you offer technical support?</span>
                <ChevronUpIcon
                  className={`${
                    open ? 'rotate-180 transform' : ''
                  } h-5 w-5 text-purple-500`}
                />
              </Disclosure.Button>
              <Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
                No.
              </Disclosure.Panel>
            </>
          )}
        </Disclosure>
      </div>
    </div>
  )
}

我们如何控制组件外部的 close/open 状态?

我认为使用 HeadlessUI 是不可能的,尽管您可以创建自己的 Disclosure 类组件。

  • 通过创建一个 disclosures 状态来存储有关披露的所有信息,将状态提升到父组件。
  • 使用 map 循环披露并呈现它们。
  • 渲染一个按钮来切换 isClose 属性 的披露并处理 aria 属性。
  • 单击按钮时,切换所单击披露项的 isOpen 值并关闭所有其他披露项。

检查下面的代码片段:

import React, { useState } from "react";
import { ChevronUpIcon } from "@heroicons/react/solid";

export default function Example() {
  const [disclosures, setDisclosures] = useState([
    {
      id: "disclosure-panel-1",
      isOpen: false,
      buttonText: "What is your refund policy?",
      panelText:
        "If you're unhappy with your purchase for any reason, email us within 90 days and we'll refund you in full, no questions asked."
    },
    {
      id: "disclosure-panel-2",
      isOpen: false,
      buttonText: "Do you offer technical support?",
      panelText: "No."
    }
  ]);

  const handleClick = (id) => {
    setDisclosures(
      disclosures.map((d) =>
        d.id === id ? { ...d, isOpen: !d.isOpen } : { ...d, isOpen: false }
      )
    );
  };

  return (
    <div className="w-full px-4 pt-16">
      <div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2 space-y-2">
        {disclosures.map(({ id, isOpen, buttonText, panelText }) => (
          <React.Fragment key={id}>
            <button
              className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75"
              onClick={() => handleClick(id)}
              aria-expanded={isOpen}
              {...(isOpen && { "aria-controls": id })}
            >
              {buttonText}
              <ChevronUpIcon
                className={`${
                  isOpen ? "rotate-180 transform" : ""
                } h-5 w-5 text-purple-500`}
              />
            </button>
            {isOpen && (
              <div className="px-4 pt-4 pb-2 text-sm text-gray-500">
                {panelText}
              </div>
            )}
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}