为了制作带有Headless UI的手风琴组件,我使用了Disclosure组件。但我有一个问题,为它的兄弟姐妹控制崩溃/扩展状态。
所以,当我打开一个兄弟时,我想关闭其他兄弟,但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>
)
}
我们如何控制组件外部的关闭/打开状态?
我认为使用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>
);
}
React有一种方法可以通过useState:实现这一点(假设您使用的是@headlessui/React)
const [disclosureState, setDisclosureState] = useState(0);
function handleDisclosureChange(state: number) {
if (state === disclosureState) {
setDisclosureState(0); // close all of them
} else {
setDisclosureState(state); // open the clicked disclosure
}
}
在每个Disclosure组件中,只需将onClick回调传递给Disclosure。按钮:
<Disclosure.Button onClick={() => handleDisclosureChange(N)} />
其中N是单击的披露的索引(使用1作为第一个披露,因为0处理所有关闭的披露)。
最后,有条件地提供披露。基于披露的面板状态:
{
disclosureState === N && (<Disclosure.Panel />)
}
其中,N是单击的披露的索引。使用此方法,一次只能打开一个公开,单击打开的公开将关闭所有公开。
您可能只需要向Disclosure.Button组件添加一些额外的道具选择器。在这种情况下,我将添加aria label='panel',就像这样…
import { Disclosure } from '@headlessui/react'
function MyDisclosure() {
return (
<Disclosure>
<Disclosure.Button aria-label="panel" className="py-2">
Is team pricing available?
</Disclosure.Button>
<Disclosure.Panel className="text-gray-500">
Yes! You can purchase a license that you can share with your entire
team.
</Disclosure.Panel>
</Disclosure>
)
}
接下来你需要用";querySelectorAll;喜欢
<button
type='button'
onClick={() => {
const panels = [...document.querySelectorAll('[aria-expanded=true][aria-label=panel]')]
panels.map((panel) => panel.click())
}}
>
</button>
这样,您只需要将"aria-expanded"更改为"true"或"false"即可展开或折叠