To make the accordion component with Headless UI, I have used Disclosure component. But I have a problem to control the collapse/expand state for it's siblings.
So, I want to close other siblings when I open one, but Disclosure component is only supporting internal render props, open and close. So, I can't control it outside of the component and can't close others when I open one.
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>
)
}
How do we control the close/open state outside of the component?
I don't think so it's possible using HeadlessUI, although you can create your own Disclosure
like component.
disclosures
state that stores all the information about the disclosures.map
and render them.isClose
property of the disclosures and also handles the aria
attributes.isOpen
value of the clicked disclosure and close all the other disclosures.Checkout the snippet below:
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>
);
}