I’m trying to build a custom Accordion component with (Adobe) React Aria. I’m building it on top of the Disclosure and DisclosureGroup components.
import { tv } from "tailwind-variants";
import { motion } from "framer-motion";
import {
UNSTABLE_Disclosure as DisclosureItem,
UNSTABLE_DisclosureGroup as Disclosure,
UNSTABLE_DisclosurePanel as DisclosurePanel,
DisclosureGroupProps as DisclosureProps,
DisclosureProps as DisclosureItemProps
} from "react-aria-components";
import ChevronLeft from "@spectrum-icons/workflow/ChevronLeft";
import { useButton, AriaButtonOptions, ButtonAria } from "@react-aria/button";
export const accordion = tv({
slots: {
accordion: "p-2",
item: "",
header: "flex flex-row"
},
variants: {
colorMode: {
dark: { accordion: "bg-slate-900 text-white" },
light: { accordion: "text-slate-800" }
}
}
});
export type AccordionProps = Omit<DisclosureProps, "id" | "style" | "isDisabled" | "className"> & Options;
export const Accordion = forwardRef(
(
{
children,
allowsMultipleExpanded,
expandedKeys,
defaultExpandedKeys,
onExpandedChange,
className,
id,
isVisible,
borderRadius,
isLoading,
error,
isDisabled
}: AccordionProps,
ref: React.LegacyRef<HTMLDivElement>
) => {
return (
<Disclosure
ref={ref}
allowsMultipleExpanded={allowsMultipleExpanded}
expandedKeys={expandedKeys}
defaultExpandedKeys={defaultExpandedKeys}
onExpandedChange={onExpandedChange}
className={accordion().accordion({ colorMode: "light", class: className })}
id={id}
isDisabled={isDisabled}
>
{children}
</Disclosure>
);
}
);
export type AccordionItemProps = Omit<DisclosureItemProps, "id" | "style" | "isDisabled" | "className"> & Options;
export const AccordionItem = forwardRef(
(
{
children,
onExpandedChange,
className,
isExpanded,
defaultExpanded,
slot,
id,
isVisible,
borderRadius,
isLoading,
error,
isDisabled
}: AccordionItemProps,
ref: React.LegacyRef<HTMLDivElement>
) => {
return (
<DisclosureItem
ref={ref}
onExpandedChange={onExpandedChange}
className={accordion().item({ class: className })}
isExpanded={isExpanded}
defaultExpanded={defaultExpanded}
slot={slot}
id={id}
isDisabled={isDisabled}
>
{children}
</DisclosureItem>
);
}
);
export type AccordionHeaderProps<T extends ElementType> = {
children: React.ReactNode;
iconProps?: AriaButtonOptions<T>;
} & Options;
export const AccordionHeader = forwardRef(
(
{
children,
iconProps,
id,
className,
isVisible,
borderRadius,
isLoading,
error,
isDisabled
}: AccordionHeaderProps<"button">,
ref
) => {
const { buttonProps } = useButton(iconProps || { elementType: "button" }, ref as RefObject<HTMLButtonElement>);
// @ts-ignore
// Type conversion for framer-motion is nessecary so defination is removed
const ariaButtonProps: Omit<ButtonAria<HTMLAttributes<unknown>>, "defination"> & { defination: undefined } = {
defination: undefined,
onAnimationStart: undefined,
...buttonProps
};
return (
<div className={accordion().header({ class: className })}>
<div>{children}</div>
<motion.button {...ariaButtonProps} disabled={isDisabled} className="ml-auto">
<ChevronLeft width={25} />
</motion.button>
</div>
);
}
);
export const AccordionContent = forwardRef(({ children }: { children: React.ReactNode }, ref) => {
return <DisclosurePanel>{children}</DisclosurePanel>;
});
Please note that I have rearranged the names of the Disclosure from React Aria to better fit my use cases:
Disclosure
is nowDisclosureItem
DisclosureGroup
is nowDisclosure
DisclosurePanel
is nowDisclosureProps
This new accordion component is used like so:
<Accordion>
<AccordionItem isExpanded>
<AccordionHeader>
test
</AccordionHeader>
<AccordionContent>
Hello, World
</AccordionContent>
</AccordionItem>
</Accordion>
As you can see I am trying to control one of the AccordionItem
s however when I preview the component all it is not expanded:
Current
Expected
I look at the DOM shows that even when isExpanded
is true
hidden
is still applied to the AccordionContent
div
:
<div class="p-2 text-slate-800" data-rac="">
<div class="react-aria-Disclosure" data-rac="">
<div class="flex flex-row">
<div>test</div>
<button type="button" class="ml-auto">
<svg
viewBox="0 0 36 36"
class="wBx8DG_spectrum-Icon wBx8DG_spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
role="img"
style="width: 25px"
>
<path
fill-rule="evenodd"
d="M12,18v0a1.988,1.988,0,0,0,.585,1.409l7.983,7.98a2,2,0,1,0,2.871-2.772l-.049-.049L16.819,18l6.572-6.57a2,2,0,0,0-2.773-2.87l-.049.049-7.983,7.98A1.988,1.988,0,0,0,12,18Z"
></path>
</svg>
</button>
</div>
<!-- Here ⬇️ -->
<div
id="react-aria3890111277-:r2:"
role="group"
aria-labelledby="react-aria3890111277-:r1:"
hidden=""
class="react-aria-DisclosurePanel"
data-rac=""
>
Hello, World
</div>
</div>
</div>
Removing hidden shows the element. So my question is how do I do this with RA?