Accordion
Yes. It adheres to the WAI-ARIA design pattern
<Accordion >
<AccordionItem >
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
Yes. It adheres to the WAI-ARIA design pattern
</AccordionContent>
</AccordionItem>
<AccordionItem >
<AccordionTrigger>Is it accessible?</AccordionTrigger>
<AccordionContent>
<Button>Hover Me</Button>
</AccordionContent>
</AccordionItem>
</Accordion>
/**
* @file Provides a flexible and customizable Accordion component for displaying collapsible content sections.
*
* The Accordion component is designed to be highly versatile and can be easily integrated into various layouts and designs.
* It allows for the creation of interactive user interfaces where content can be expanded or collapsed as needed.
*
* @example
* // Basic usage:
* <Accordion>
* <AccordionItem>
* <AccordionTrigger>Section 1</AccordionTrigger>
* <AccordionContent>Content for Section 1</AccordionContent>
* </AccordionItem>
* <AccordionItem open={true}>
* <AccordionTrigger>Section 2</AccordionTrigger>
* <AccordionContent>Content for Section 2</AccordionContent>
* </AccordionItem>
* </Accordion>
*/
import React, { useState } from 'react';
/**
* The main Accordion component that wraps around individual AccordionItem components.
*
* @param {object} props - The component props.
* @param {React.ReactNode} props.children - The child components to be rendered within the Accordion.
* @param {string} [props.className] - Optional className to apply to the Accordion container.
* @returns {JSX.Element} The Accordion component.
*/
export const Accordion = ({ children, className }) => {
return (
<div className={`p-4 border border-gray-500/50 rounded-md ${className}`}>
{children}
</div>
);
};
/**
* Represents a single item within the Accordion, containing a trigger and its corresponding content.
*
* @param {object} props - The component props.
* @param {React.ReactNode} props.children - The child components of the AccordionItem, including AccordionTrigger and AccordionContent.
* @param {string} [props.className] - Optional className to apply to the AccordionItem container.
* @param {boolean} [props.open=false] - Whether the AccordionItem should be initially open.
* @returns {JSX.Element} The AccordionItem component.
*/
export const AccordionItem = ({ children, className, open = false }) => {
const [isOpen, setIsOpen] = useState(open);
const toggleOpen = () => {
setIsOpen(!isOpen);
};
return (
<div className={`w-full mb-2 ${className}`}>
{React.Children.map(children, (child) => {
if (child.type === AccordionTrigger) {
return React.cloneElement(child, {
isOpen: isOpen,
onClick: toggleOpen,
});
} else if (child.type === AccordionContent) {
return React.cloneElement(child, {
isOpen: isOpen,
});
}
return null;
})}
<hr className='border-t-gray-500/50' />
</div>
);
};
/**
* The trigger element that toggles the visibility of the AccordionContent.
*
* @param {object} props - The component props.
* @param {boolean} props.isOpen - Whether the AccordionItem is currently open.
* @param {Function} props.onClick - The function to be called when the trigger is clicked.
* @param {React.ReactNode} props.children - The content to be displayed within the trigger.
* @param {string} [props.className] - Optional className to apply to the AccordionTrigger button.
* @returns {JSX.Element} The AccordionTrigger component.
*/
export const AccordionTrigger = ({ isOpen, onClick, children, className }) => {
return (
<button
onClick={onClick}
className={`w-full px-4 py-3 text-left font-medium flex justify-between items-center focus:outline-none ${className}`}
>
{children}
<svg
className={` h-4 w-4 transition-transform transform ${isOpen ? 'rotate-180' : ''}`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
);
};
/**
* The content section that is displayed or hidden based on the state of the AccordionItem.
*
* @param {object} props - The component props.
* @param {boolean} props.isOpen - Whether the AccordionItem is currently open.
* @param {React.ReactNode} props.children - The content to be displayed within the AccordionContent.
* @param {string} [props.className] - Optional className to apply to the AccordionContent container.
* @returns {JSX.Element} The AccordionContent component.
*/
export const AccordionContent = ({ isOpen, children, className }) => {
return (
<div
className={`transition-max-height duration-300 ease-in-out transform ${isOpen ? 'min-h-fit overflow-visible' : 'max-h-0 overflow-hidden'} ${className}`}
>
<div className='px-4 py-3'>
{children}
</div>
</div>
);
};