diff --git a/src/components/Accordion.stories.tsx b/src/components/Accordion.stories.tsx index a2056da81..7c2dbfc4c 100644 --- a/src/components/Accordion.stories.tsx +++ b/src/components/Accordion.stories.tsx @@ -31,6 +31,21 @@ export function AccordionVariations() { Our modern approach to homebuilding makes the process easier and more personal than ever before. + {}}> +
+ Our modern approach to homebuilding makes the process easier and more personal than ever before. +
+
+ +
+ Our modern approach to homebuilding makes the process easier and more personal than ever before. +
+
+ {}} compact> +
+ Our modern approach to homebuilding makes the process easier and more personal than ever before. +
+
); } diff --git a/src/components/Accordion.test.tsx b/src/components/Accordion.test.tsx index 63e51c4b0..cd69c2750 100644 --- a/src/components/Accordion.test.tsx +++ b/src/components/Accordion.test.tsx @@ -1,4 +1,4 @@ -import { render } from "src/utils/rtl"; +import { click, render } from "src/utils/rtl"; import { Accordion } from "./Accordion"; describe(Accordion, () => { @@ -72,4 +72,38 @@ describe(Accordion, () => { // And the content is not displayed expect(r.query.accordion_content).not.toBeInTheDocument(); }); + + it("calls the titleOnClick function when the title is clicked", async () => { + // Given an accordion component with titleOnClick set + const titleOnClick = jest.fn(); + // When rendered + const r = await render( + + Test description + , + ); + + // Then the titleOnClick function is called when the title is clicked + click(r.accordion_title); + expect(titleOnClick).toHaveBeenCalled(); + }); + + it("alters expando behavior when titleOnClick is provided", async () => { + // When rendered with a titleOnClick set + const r = await render( + {}}> + Test description + , + ); + + // when the title is clicked + click(r.accordion_title); + // then the content is not displayed + expect(r.query.accordion_content).not.toBeInTheDocument(); + + // when bar is clicked + click(r.accordion_toggle); + // then the content is displayed + expect(r.accordion_content).toHaveTextContent("Test description"); + }); }); diff --git a/src/components/Accordion.tsx b/src/components/Accordion.tsx index 5fd43e152..435f0b0ad 100644 --- a/src/components/Accordion.tsx +++ b/src/components/Accordion.tsx @@ -1,5 +1,5 @@ import { useId, useResizeObserver } from "@react-aria/utils"; -import { Dispatch, ReactNode, SetStateAction, useCallback, useEffect, useRef, useState } from "react"; +import { Dispatch, ReactNode, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFocusRing } from "react-aria"; import { Icon } from "src/components/Icon"; import { Css, Only, Padding, Palette, Xss } from "src/Css"; @@ -23,6 +23,8 @@ export interface AccordionProps { */ index?: number; setExpandedIndex?: Dispatch>; + /** Turns the title into a button. If provided, disables expand/collapse on title text */ + titleOnClick?: VoidFunction; /** Used by Accordion list. Sets default padding to 0 for nested accordions */ omitPadding?: boolean; /** Styles overrides for padding */ @@ -43,10 +45,11 @@ export function Accordion>(props: AccordionProps bottomBorder = false, index, setExpandedIndex, + titleOnClick, omitPadding = false, xss, } = props; - const testIds = useTestIds(props, "accordion"); + const tid = useTestIds(props, "accordion"); const id = useId(); const [expanded, setExpanded] = useState(defaultExpanded && !disabled); const { isFocusVisible, focusProps } = useFocusRing(); @@ -76,51 +79,62 @@ export function Accordion>(props: AccordionProps }, [expanded, setContentHeight]); useResizeObserver({ ref: contentRef, onResize }); + const toggle = useCallback(() => { + setExpanded((prev) => !prev); + if (setExpandedIndex) setExpandedIndex(index); + }, [index, setExpandedIndex]); + + const touchableStyle = useMemo( + () => ({ + ...Css.df.jcsb.gapPx(12).aic.p2.baseMd.outline("none").onHover.bgGray100.if(!!titleOnClick).baseSb.$, + ...(compact && Css.smMd.pl2.prPx(10).py1.bgGray100.mbPx(4).br8.onHover.bgGray200.$), + ...(compact && !!titleOnClick && Css.br0.$), + ...(disabled && Css.gray500.$), + ...(isFocusVisible && Css.boxShadow(`inset 0 0 0 2px ${Palette.Blue700}`).$), + ...xss, + }), + [compact, disabled, isFocusVisible, titleOnClick, xss], + ); + return (
- + +
+ ) : ( + + {title} + + + )}
{expanded && ( -
+
{children}
)} @@ -129,6 +143,20 @@ export function Accordion>(props: AccordionProps ); } +function RotatingChevronIcon(props: { expanded: boolean }) { + return ( + + + + ); +} + export type AccordionSize = "xs" | "sm" | "md" | "lg"; const accordionSizes: Record = {