diff --git a/.changeset/honest-apes-rush.md b/.changeset/honest-apes-rush.md
new file mode 100644
index 000000000..61845343d
--- /dev/null
+++ b/.changeset/honest-apes-rush.md
@@ -0,0 +1,5 @@
+---
+"@khanacademy/wonder-blocks-accordion": minor
+---
+
+Add animations to Accordion
diff --git a/__docs__/wonder-blocks-accordion/accordion-section.argtypes.tsx b/__docs__/wonder-blocks-accordion/accordion-section.argtypes.tsx
index 337f19b02..a8a39f0cb 100644
--- a/__docs__/wonder-blocks-accordion/accordion-section.argtypes.tsx
+++ b/__docs__/wonder-blocks-accordion/accordion-section.argtypes.tsx
@@ -36,10 +36,7 @@ export default {
left-to-right language (and on the right of a right-to-left
language), and "end" means it’s on the right of a left-to-right
language (and on the left of a right-to-left language).
- Defaults to "end".
- If this prop is specified both here in the \`AccordionSection\`
- and within the \`Accordion\` component, the Accordion’s
- caretPosition value is prioritized.`,
+ Defaults to "end".`,
defaultValue: "end",
table: {
defaultValue: {summary: "end"},
@@ -78,6 +75,16 @@ export default {
type: {summary: "boolean"},
},
},
+ animated: {
+ control: {type: "boolean"},
+ description: `Whether to include animation on the header. This should
+ be false if the user has \`prefers-reduced-motion\` opted in.
+ Defaults to false.`,
+ table: {
+ defaultValue: {summary: "false"},
+ type: {summary: "boolean"},
+ },
+ },
onToggle: {
control: {type: null},
description: "Called when the header is clicked.",
diff --git a/__docs__/wonder-blocks-accordion/accordion-section.stories.tsx b/__docs__/wonder-blocks-accordion/accordion-section.stories.tsx
index 78d314ca1..582e947bb 100644
--- a/__docs__/wonder-blocks-accordion/accordion-section.stories.tsx
+++ b/__docs__/wonder-blocks-accordion/accordion-section.stories.tsx
@@ -16,6 +16,49 @@ import packageConfig from "../../packages/wonder-blocks-icon/package.json";
import AccordionSectionArgtypes from "./accordion-section.argtypes";
+/**
+ * An AccordionSection displays a section of content that can be shown or
+ * hidden by clicking its header. This is generally used within the Accordion
+ * component, but it can also be used on its own if you need only one
+ * collapsible section.
+ *
+ * ### Usage
+ *
+ * ```jsx
+ * import {
+ * Accordion,
+ * AccordionSection
+ * } from "@khanacademy/wonder-blocks-accordion";
+ *
+ * // Within an Accordion
+ *
+ *
+ * This is the information present in the first section
+ *
+ *
+ * This is the information present in the second section
+ *
+ *
+ * This is the information present in the third section
+ *
+ *
+ *
+ * // On its own, controlled
+ * const [expanded, setExpanded] = React.useState(false);
+ *
+ * This is the information present in the standalone section
+ *
+ *
+ * // On its own, uncontrolled
+ *
+ * This is the information present in the standalone section
+ *
+ * ```
+ */
export default {
title: "Accordion / AccordionSection",
component: AccordionSection,
@@ -371,6 +414,36 @@ export const CornerKinds: StoryComponentType = {
},
};
+/**
+ * An AccordionSection can be animated using the `animated` prop.
+ * This animation includes the caret, the expansion/collapse, and the
+ * border radius.
+ *
+ * If the user has `prefers-reduced-motion` opted in, this animation should
+ * be disabled. This can be done by passing `animated={false}` to
+ * the AccordionSection.
+ *
+ * If `animated` is specified both here in the AccordionSection
+ * and within a parent Accordion component, the Accordion's
+ * `animated` value is prioritized.
+ */
+export const WithAnimation: StoryComponentType = {
+ render: () => {
+ return (
+
+ Something
+
+ );
+ },
+};
+
+WithAnimation.parameters = {
+ chromatic: {
+ // Disabling because we cannot visually test this in chromatic.
+ disableSnapshot: true,
+ },
+};
+
/**
* An AccordionSection can have custom styles passed in. In this example,
* the AccordionSection has a gray background and a border, as well as
@@ -449,9 +522,15 @@ export const WithTag: StoryComponentType = {
},
};
+const mobile = "@media (max-width: 1023px)";
+
const styles = StyleSheet.create({
sideBySide: {
flexDirection: "row",
+
+ [mobile]: {
+ flexDirection: "column",
+ },
},
fullWidth: {
width: "100%",
diff --git a/__docs__/wonder-blocks-accordion/accordion.argtypes.tsx b/__docs__/wonder-blocks-accordion/accordion.argtypes.tsx
index a231b7588..70c503973 100644
--- a/__docs__/wonder-blocks-accordion/accordion.argtypes.tsx
+++ b/__docs__/wonder-blocks-accordion/accordion.argtypes.tsx
@@ -74,6 +74,18 @@ export default {
required: false,
},
},
+ animated: {
+ control: {type: "boolean"},
+ description: `Whether to include animation on the header. This should
+ be false if the user has \`prefers-reduced-motion\` opted in.
+ Defaults to false.`,
+ defaultValue: false,
+ table: {
+ defaultValue: {summary: false},
+ type: {summary: "boolean"},
+ },
+ type: {name: "boolean", required: false},
+ },
style: {
control: {type: "object"},
description: "Custom styles for the overall accordion container.",
diff --git a/__docs__/wonder-blocks-accordion/accordion.stories.tsx b/__docs__/wonder-blocks-accordion/accordion.stories.tsx
index 5194fee1d..4c1d0f82c 100644
--- a/__docs__/wonder-blocks-accordion/accordion.stories.tsx
+++ b/__docs__/wonder-blocks-accordion/accordion.stories.tsx
@@ -6,6 +6,7 @@ import {
Accordion,
AccordionSection,
} from "@khanacademy/wonder-blocks-accordion";
+import Button from "@khanacademy/wonder-blocks-button";
import {View} from "@khanacademy/wonder-blocks-core";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import {tokens} from "@khanacademy/wonder-blocks-theming";
@@ -16,6 +17,35 @@ import packageConfig from "../../packages/wonder-blocks-icon/package.json";
import AccordionArgtypes from "./accordion.argtypes";
+/**
+ * An accordion displays a vertically stacked list of sections, each of which
+ * contains content that can be shown or hidden by clicking its header.
+ *
+ * The Wonder Blocks Accordion component is a styled wrapper for a list of
+ * AccordionSection components. It also wraps the AccordionSection
+ * components in list items.
+ *
+ * ### Usage
+ *
+ * ```jsx
+ * import {
+ * Accordion,
+ * AccordionSection
+ * } from "@khanacademy/wonder-blocks-accordion";
+ *
+ *
+ *
+ * This is the information present in the first section
+ *
+ *
+ * This is the information present in the second section
+ *
+ *
+ * This is the information present in the third section
+ *
+ *
+ * ```
+ */
export default {
title: "Accordion / Accordion",
component: Accordion,
@@ -64,16 +94,41 @@ export const Default: StoryComponentType = {
*/
export const AllowMultipleExpanded: StoryComponentType = {
render: () => (
-
-
- Allow multiple expanded
+
+
+ Allow multiple expanded (default){exampleSections}
-
- Allow only one expanded
-
- {exampleSections}
-
+
+
+ Allow only one expanded
+
+ {exampleSections}
+
+
+
+ Allow only one expanded
+
+ {exampleSections}
+
+
+
+ Allow only one expanded
+
+ {exampleSections}
+
+
),
@@ -221,6 +276,141 @@ export const WithInitialExpandedIndex: StoryComponentType = {
},
};
+/**
+ * An Accordion can be animated using the `animated` prop. This
+ * animation includes the caret, the expansion/collapse, and the last
+ * section's border radius. In this example, animated accordions with
+ * different corner kinds are shown to demonstrate the border radius transition,
+ * as well as accordions with `allowMultipleExpanded` set to `false`, and
+ * an accordion with sections of different heights.
+ *
+ * If the user has `prefers-reduced-motion` opted in, this animation should
+ * be disabled. This can be done by passing `animated={false}` to
+ * the Accordion.
+ *
+ * If `animated` is specified both here in the Accordion
+ * and within a child AccordionSection component, the Accordion's
+ * `animated` value is prioritized.
+ */
+export const WithAnimation: StoryComponentType = {
+ render: () => {
+ return (
+
+
+
+ cornerKind: square
+
+ {exampleSections}
+
+
+
+ cornerKind: rounded
+
+ {exampleSections}
+
+
+
+ cornerKind: rounded-per-section
+
+ {exampleSections}
+
+
+
+
+
+
+ cornerKind: square, allowMultipleExpanded: false
+
+
+ {exampleSections}
+
+
+
+
+ cornerKind: rounded, allowMultipleExpanded: false
+
+
+ {exampleSections}
+
+
+
+
+ cornerKind: rounded-per-section,
+ allowMultipleExpanded: false
+
+
+ {exampleSections}
+
+
+
+
+
+ With unevenly sided sections, allowMultipleExpanded:
+ false
+
+
+
+
+ This is the information present in the first
+ section
+
+
+
+
+ This is the information present in the second
+ section
+
+
+
+
+ This is the information present in the third
+ section
+
+
+
+
+
+ );
+ },
+};
+
+WithAnimation.parameters = {
+ chromatic: {
+ // Disabling because we cannot visually test this in chromatic.
+ disableSnapshot: true,
+ },
+};
+
/**
* An Accordion with custom styles. The custom styles in this example
* include a pink border and extra padding.
@@ -282,9 +472,67 @@ SingleSection.parameters = {
},
};
+/**
+ * This is an example of an Accordion with many sections, as well as
+ * a lot of content within each section.
+ */
+export const LongSections: StoryComponentType = {
+ name: "Long sections (performance check)",
+ render: function Render() {
+ const [shown, setShown] = React.useState(false);
+
+ return (
+
+
+ {shown && (
+
+ {Array(20).fill(
+
+
+
+
+
+
+ ,
+ )}
+
+ )}
+
+ );
+ },
+};
+
+LongSections.parameters = {
+ chromatic: {
+ // Disabling because we cannot visually test this in chromatic.
+ disableSnapshot: true,
+ },
+};
+
+const mobile = "@media (max-width: 1023px)";
+
const styles = StyleSheet.create({
sideBySide: {
flexDirection: "row",
+
+ [mobile]: {
+ flexDirection: "column",
+ },
},
fullWidth: {
width: "100%",
@@ -295,4 +543,8 @@ const styles = StyleSheet.create({
space: {
margin: tokens.spacing.xSmall_8,
},
+ button: {
+ width: "fit-content",
+ marginBottom: tokens.spacing.medium_16,
+ },
});
diff --git a/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section-header.test.tsx b/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section-header.test.tsx
index 06c454bf4..406986340 100644
--- a/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section-header.test.tsx
+++ b/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section-header.test.tsx
@@ -12,6 +12,7 @@ describe("AccordionSectionHeader", () => {
caretPosition="end"
cornerKind="square"
expanded={false}
+ animated={false}
onClick={() => {}}
sectionContentUniqueId="section-content-unique-id"
isFirstSection={false}
@@ -34,6 +35,7 @@ describe("AccordionSectionHeader", () => {
caretPosition="end"
cornerKind="square"
expanded={false}
+ animated={false}
onClick={() => {}}
sectionContentUniqueId="section-content-unique-id"
isFirstSection={false}
@@ -59,6 +61,7 @@ describe("AccordionSectionHeader", () => {
caretPosition="end"
cornerKind="square"
expanded={false}
+ animated={false}
onClick={() => {}}
sectionContentUniqueId="section-content-unique-id"
isFirstSection={false}
@@ -81,6 +84,7 @@ describe("AccordionSectionHeader", () => {
caretPosition="end"
cornerKind="square"
expanded={false}
+ animated={false}
onClick={onClickSpy}
sectionContentUniqueId="section-content-unique-id"
isFirstSection={false}
@@ -92,4 +96,54 @@ describe("AccordionSectionHeader", () => {
// Assert
expect(onClickSpy).toHaveBeenCalledTimes(1);
});
+
+ test("includes transition styles when animated is true", () => {
+ // Arrange
+ render(
+ {}}
+ sectionContentUniqueId="section-content-unique-id"
+ isFirstSection={false}
+ isLastSection={false}
+ />,
+ );
+
+ // Act
+ const header = screen.getByRole("button");
+
+ // Assert
+ expect(header).toHaveStyle({
+ transition: "border-radius 300ms",
+ });
+ });
+
+ test("does not include transition styles when animated is false", () => {
+ // Arrange
+ render(
+ {}}
+ sectionContentUniqueId="section-content-unique-id"
+ isFirstSection={false}
+ isLastSection={false}
+ />,
+ );
+
+ // Act
+ const header = screen.getByRole("button");
+
+ // Assert
+ expect(header).not.toHaveStyle({
+ transition: "border-radius 300ms",
+ });
+ });
});
diff --git a/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section.test.tsx b/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section.test.tsx
index 8049a792d..ce3419f19 100644
--- a/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section.test.tsx
+++ b/packages/wonder-blocks-accordion/src/components/__tests__/accordion-section.test.tsx
@@ -17,7 +17,7 @@ describe("AccordionSection", () => {
// Assert
expect(screen.getByText("Title")).toBeVisible();
- expect(screen.queryByText("Section content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section content")).not.toBeVisible();
});
test("renders with open panel when expanded is true", () => {
@@ -114,7 +114,7 @@ describe("AccordionSection", () => {
// Assert
// Make sure the section has closed after clicking
- expect(screen.queryByText("Section content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section content")).not.toBeVisible();
// Repeat clicking to confirm behavior
button.click();
expect(screen.getByText("Section content")).toBeVisible();
@@ -129,7 +129,7 @@ describe("AccordionSection", () => {
// Act
// Make sure the section is closed at first
- expect(screen.queryByText("Section content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section content")).not.toBeVisible();
const button = screen.getByRole("button", {name: "Title"});
button.click();
@@ -139,7 +139,7 @@ describe("AccordionSection", () => {
expect(screen.getByText("Section content")).toBeVisible();
// Repeat clicking to confirm behavior
button.click();
- expect(screen.queryByText("Section content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section content")).not.toBeVisible();
});
test("is h2 by default", () => {
@@ -260,4 +260,58 @@ describe("AccordionSection", () => {
"border-radius": "12px",
});
});
+
+ test("includes transition when animated is true", () => {
+ // Arrange
+ render(
+
+ Section content
+ ,
+ {wrapper: RenderStateRoot},
+ );
+
+ // Act
+ const wrapper = screen.getByTestId("accordion-section-test-id");
+ const header = screen.getByTestId("accordion-section-header");
+
+ // Assert
+ expect(wrapper).toHaveStyle({
+ transition: "grid-template-rows 300ms",
+ });
+ expect(header).toHaveStyle({
+ transition: "border-radius 300ms",
+ });
+ });
+
+ test("does not include transition when animated is false", () => {
+ // Arrange
+ render(
+
+ Section content
+ ,
+ {wrapper: RenderStateRoot},
+ );
+
+ // Act
+ const wrapper = screen.getByTestId("accordion-section-test-id");
+ const header = screen.getByTestId("accordion-section-header");
+
+ // Assert
+ expect(wrapper).not.toHaveStyle({
+ transition: "grid-template-rows 300ms",
+ });
+ expect(header).not.toHaveStyle({
+ transition: "border-radius 300ms",
+ });
+ });
});
diff --git a/packages/wonder-blocks-accordion/src/components/__tests__/accordion.test.tsx b/packages/wonder-blocks-accordion/src/components/__tests__/accordion.test.tsx
index 2fcd7ff7a..854c8416a 100644
--- a/packages/wonder-blocks-accordion/src/components/__tests__/accordion.test.tsx
+++ b/packages/wonder-blocks-accordion/src/components/__tests__/accordion.test.tsx
@@ -80,8 +80,8 @@ describe("Accordion", () => {
button2.click();
// Assert
- expect(screen.queryByText("Section 1 content")).not.toBeInTheDocument();
- expect(screen.queryByText("Section 2 content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section 1 content")).not.toBeVisible();
+ expect(screen.queryByText("Section 2 content")).not.toBeVisible();
});
test("initialExpandedIndex opens the correct section", () => {
@@ -103,9 +103,9 @@ describe("Accordion", () => {
// Act
// Assert
- expect(screen.queryByText("Section 1 content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section 1 content")).not.toBeVisible();
expect(screen.getByText("Section 2 content")).toBeVisible();
- expect(screen.queryByText("Section 3 content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section 3 content")).not.toBeVisible();
});
test("only allows one section to be open at a time when allowMultipleExpanded is false", () => {
@@ -130,8 +130,8 @@ describe("Accordion", () => {
button.click();
// Assert
- expect(screen.queryByText("Section 1 content")).not.toBeInTheDocument();
- expect(screen.queryByText("Section 2 content")).not.toBeInTheDocument();
+ expect(screen.queryByText("Section 1 content")).not.toBeVisible();
+ expect(screen.queryByText("Section 2 content")).not.toBeVisible();
expect(screen.getByText("Section 3 content")).toBeVisible();
});
@@ -359,6 +359,36 @@ describe("Accordion", () => {
});
});
+ test("prioritizes the parent's animated prop", () => {
+ // Arrange
+ render(
+
+ {[
+
+ Section content
+ ,
+ ]}
+ ,
+ {wrapper: RenderStateRoot},
+ );
+
+ // Act
+ const sectionHeader = screen.getByTestId("section-header-test-id");
+
+ // Assert
+ // The parent has animated=true, so the child's animated=false
+ // should be overridden.
+ expect(sectionHeader).toHaveStyle({
+ // The existence of the transition style means that the
+ // accordion is animated.
+ transition: "border-radius 300ms",
+ });
+ });
+
test("applies style to the wrapper", () => {
// Arrange
render(
diff --git a/packages/wonder-blocks-accordion/src/components/accordion-section-header.tsx b/packages/wonder-blocks-accordion/src/components/accordion-section-header.tsx
index 44e82a70a..b28262b33 100644
--- a/packages/wonder-blocks-accordion/src/components/accordion-section-header.tsx
+++ b/packages/wonder-blocks-accordion/src/components/accordion-section-header.tsx
@@ -22,6 +22,9 @@ type Props = {
cornerKind: AccordionCornerKindType;
// Whether the section is expanded or not.
expanded: boolean;
+ // Whether to include animation on the header. This should be false
+ // if the user has `prefers-reduced-motion` opted in. Defaults to false.
+ animated: boolean;
// Called on header click.
onClick?: () => void;
// The ID for the content that the header's `aria-controls` should
@@ -48,6 +51,7 @@ const AccordionSectionHeader = (props: Props) => {
caretPosition,
cornerKind,
expanded,
+ animated,
onClick,
sectionContentUniqueId,
headerStyle,
@@ -75,6 +79,7 @@ const AccordionSectionHeader = (props: Props) => {
testId={testId}
style={[
styles.headerWrapper,
+ animated && styles.headerWrapperWithAnimation,
caretPosition === "start" && styles.headerWrapperCaretStart,
roundedTop && styles.roundedTop,
roundedBottom && styles.roundedBottom,
@@ -108,6 +113,7 @@ const AccordionSectionHeader = (props: Props) => {
color={tokens.color.offBlack64}
size="small"
style={[
+ animated && styles.iconWithAnimation,
caretPosition === "start"
? styles.iconStart
: styles.iconEnd,
@@ -126,6 +132,7 @@ const AccordionSectionHeader = (props: Props) => {
// a 1px gap between the border and the outline. To fix this, we
// subtract 1 from the border radius.
const INNER_BORDER_RADIUS = tokens.spacing.small_12 - 1;
+const ANIMATION_LENGTH = "300ms";
const styles = StyleSheet.create({
heading: {
@@ -149,6 +156,9 @@ const styles = StyleSheet.create({
outline: `2px solid ${tokens.color.blue}`,
},
},
+ headerWrapperWithAnimation: {
+ transition: `border-radius ${ANIMATION_LENGTH}`,
+ },
headerWrapperCaretStart: {
flexDirection: "row-reverse",
},
@@ -180,6 +190,9 @@ const styles = StyleSheet.create({
paddingInlineEnd: tokens.spacing.medium_16,
paddingInlineStart: tokens.spacing.small_12,
},
+ iconWithAnimation: {
+ transition: `transform ${ANIMATION_LENGTH}`,
+ },
iconExpanded: {
// Turn the caret upside down
transform: "rotate(180deg)",
diff --git a/packages/wonder-blocks-accordion/src/components/accordion-section.tsx b/packages/wonder-blocks-accordion/src/components/accordion-section.tsx
index 46e530165..dc3894b11 100644
--- a/packages/wonder-blocks-accordion/src/components/accordion-section.tsx
+++ b/packages/wonder-blocks-accordion/src/components/accordion-section.tsx
@@ -61,6 +61,15 @@ type Props = AriaProps & {
* manages the expanded state of the AccordionSection.
*/
expanded?: boolean;
+ /**
+ * Whether to include animation on the header. This should be false
+ * if the user has `prefers-reduced-motion` opted in. Defaults to false.
+ *
+ * If this prop is specified both here in the AccordionSection and
+ * within a parent Accordion component, the Accordion’s animated
+ * value is prioritized.
+ */
+ animated?: boolean;
/**
* Called when the header is clicked.
* Takes the new expanded state as an argument. This way, the function
@@ -108,7 +117,7 @@ type Props = AriaProps & {
/**
* An AccordionSection displays a section of content that can be shown or
* hidden by clicking its header. This is generally used within the Accordion
- * component, but it can also be used on its own if you need only
+ * component, but it can also be used on its own if you need only one
* collapsible section.
*
* ### Usage
@@ -132,7 +141,7 @@ type Props = AriaProps & {
*
*
*
- * // On its own
+ * // On its own, controlled
* const [expanded, setExpanded] = React.useState(false);
*
* This is the information present in the standalone section
*
+ *
+ * // On its own, uncontrolled
+ *
+ * This is the information present in the standalone section
+ *
* ```
*/
const AccordionSection = React.forwardRef(function AccordionSection(
@@ -152,6 +166,7 @@ const AccordionSection = React.forwardRef(function AccordionSection(
id,
header,
expanded,
+ animated = false,
onToggle,
caretPosition = "end",
cornerKind = "rounded",
@@ -209,7 +224,15 @@ const AccordionSection = React.forwardRef(function AccordionSection(
return (
- {/* The content is the section that expands and closes. */}
- {expandedState ? (
-
- {typeof children === "string" ? (
- {children}
- ) : (
- children
- )}
-
- ) : null}
+
+ {typeof children === "string" ? (
+ {children}
+ ) : (
+ children
+ )}
+
);
});
const styles = StyleSheet.create({
wrapper: {
- flexDirection: "column",
+ // Use grid layout for clean animations.
+ display: "grid",
boxSizing: "border-box",
},
+ wrapperWithAnimation: {
+ transition: "grid-template-rows 300ms",
+ },
+ wrapperCollapsed: {
+ gridTemplateRows: "min-content 0fr",
+ },
+ wrapperExpanded: {
+ gridTemplateRows: "min-content 1fr",
+ },
contentWrapper: {
+ overflow: "hidden",
+ },
+ conentWrapperCollapsed: {
+ // Make sure screen readers don't read the content when it's
+ // collapsed.
+ visibility: "hidden",
+ },
+ contentWrapperExpanded: {
+ visibility: "visible",
// Add a small margin to the top of the content block so that the
// header outline doesn't overlap with the content (and the content
// doesn't overlap with the header outline).
@@ -307,7 +350,6 @@ const _generateStyles = (
// because it cuts off the header's focus outline.
borderEndEndRadius: tokens.spacing.small_12,
borderEndStartRadius: tokens.spacing.small_12,
- overflow: "hidden",
};
if (isFirstSection) {
@@ -339,7 +381,6 @@ const _generateStyles = (
// because it cuts off the header's focus outline.
borderEndEndRadius: tokens.spacing.small_12,
borderEndStartRadius: tokens.spacing.small_12,
- overflow: "hidden",
};
}
diff --git a/packages/wonder-blocks-accordion/src/components/accordion.tsx b/packages/wonder-blocks-accordion/src/components/accordion.tsx
index 76bf3471b..770e8ed07 100644
--- a/packages/wonder-blocks-accordion/src/components/accordion.tsx
+++ b/packages/wonder-blocks-accordion/src/components/accordion.tsx
@@ -60,6 +60,15 @@ type Props = AriaProps & {
* value is prioritized.
*/
cornerKind?: AccordionCornerKindType;
+ /**
+ * Whether to include animation on the header. This should be false
+ * if the user has `prefers-reduced-motion` opted in. Defaults to false.
+ *
+ * If this prop is specified both here in the Accordion and within
+ * a child AccordionSection component, the Accordion’s animated
+ * value is prioritized.
+ */
+ animated?: boolean;
/**
* Custom styles for the overall accordion container.
*/
@@ -106,6 +115,7 @@ const Accordion = React.forwardRef(function Accordion(
allowMultipleExpanded = true,
caretPosition,
cornerKind = "rounded",
+ animated,
style,
...ariaProps
} = props;
@@ -147,6 +157,7 @@ const Accordion = React.forwardRef(function Accordion(
caretPosition: childCaretPosition,
cornerKind: childCornerKind,
onToggle: childOnToggle,
+ animated: childanimated,
} = child.props;
const isFirstChild = index === 0;
@@ -165,6 +176,8 @@ const Accordion = React.forwardRef(function Accordion(
// Don't use the AccordionSection's expanded prop
// when it's rendered within Accordion.
expanded: sectionsOpened[index],
+ // Prioritize the Accordion's animated
+ animated: animated ?? childanimated,
onToggle: () =>
handleSectionClick(index, childOnToggle),
isFirstSection: isFirstChild,