Skip to content

Commit

Permalink
feat(accordion): add data-testid, improve ids, add interaction tests (#…
Browse files Browse the repository at this point in the history
…617)

* feat(accordion): add data-testid prop
 - add data-testid
 - reformat component

* refactor(accordion): minor code conventions (lint) refactoring
 - remove redundant const

* fix(accordion): improve AccordionItem/ExpandCollapse ids
 - ExpandCollapse component have to get prop id
   for correct A11y (aria-controls)
 - AccordionItem now getting id on base of Accordion-Id and Index
 - If Accordion-Id not specified we are taking default COMPONENT_ID

* test(accordion): add interaction tests for single-active mode
 - add interactions tests
 - connect tests to storybook
 - minor fix: remove unused import

* test(accordion): sync snapshot according to last changes
 - data-testid
 - ids improvements

* test(accordion): check that aria-expanded of the first item is false
 - add additional expectation - after switch to another item:
   aria-expanded of the first item is false
 - according to review feedback
  • Loading branch information
niksa-monday authored Mar 31, 2022
1 parent fa100e4 commit 6b884b8
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 85 deletions.
112 changes: 66 additions & 46 deletions src/components/Accordion/Accordion/Accordion.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,77 @@ import cx from "classnames";
import useMergeRefs from "../../../hooks/useMergeRefs";
import "./Accordion.scss";

const Accordion = forwardRef(({ children: originalChildren, allowMultiple, defaultIndex, className, id }, ref) => {
const componentRef = useRef(null);
const mergedRef = useMergeRefs({ refs: [ref, componentRef] });
const COMPONENT_ID = "monday-accordion";

const [expandedItems, setExpandedItems] = useState(defaultIndex);
function defineChildId(index, props, accordionId) {
if (props.id) {
return props.id;
}
if (accordionId) {
return `${accordionId}--item-${index}`;
}
return `${COMPONENT_ID}--item-${index}`;
}

const children = useMemo(() => React.Children.toArray(originalChildren), [originalChildren]);
const Accordion = forwardRef(
({ children: originalChildren, allowMultiple, "data-testid": dataTestId, defaultIndex, className, id }, ref) => {
const componentRef = useRef(null);
const mergedRef = useMergeRefs({ refs: [ref, componentRef] });

const isChildExpanded = useCallback(
itemIndex => {
return expandedItems.includes(itemIndex);
},
[expandedItems]
);
const [expandedItems, setExpandedItems] = useState(defaultIndex);

const onChildClick = useCallback(
itemIndex => {
if (allowMultiple) {
const newExpandedItems = [...expandedItems];
if (isChildExpanded(itemIndex)) {
const index = newExpandedItems.indexOf(itemIndex);
if (index > -1) {
newExpandedItems.splice(index, 1);
const children = useMemo(() => React.Children.toArray(originalChildren), [originalChildren]);

const isChildExpanded = useCallback(
itemIndex => {
return expandedItems.includes(itemIndex);
},
[expandedItems]
);

const onChildClick = useCallback(
itemIndex => {
if (allowMultiple) {
const newExpandedItems = [...expandedItems];
if (isChildExpanded(itemIndex)) {
const index = newExpandedItems.indexOf(itemIndex);
if (index > -1) {
newExpandedItems.splice(index, 1);
}
} else {
newExpandedItems.push(itemIndex);
}
} else {
newExpandedItems.push(itemIndex);
setExpandedItems(newExpandedItems);
return;
}
setExpandedItems(newExpandedItems);
return;
}

setExpandedItems([itemIndex]);
},
[isChildExpanded, expandedItems, allowMultiple]
);
setExpandedItems([itemIndex]);
},
[isChildExpanded, expandedItems, allowMultiple]
);

const renderChildElements = useMemo(() => {
const childElements = React.Children.map(children, (child, itemIndex) => {
return React.cloneElement(child, {
...child?.props,
onClickAccordionCallback: () => {
onChildClick(itemIndex);
},
open: isChildExpanded(itemIndex)
const renderChildElements = useMemo(() => {
return React.Children.map(children, (child, itemIndex) => {
const originalProps = { ...child?.props };
const childId = defineChildId(itemIndex, originalProps, id);
return React.cloneElement(child, {
...originalProps,
id: childId,
onClickAccordionCallback: () => {
onChildClick(itemIndex);
},
open: isChildExpanded(itemIndex)
});
});
});

return childElements;
}, [isChildExpanded, onChildClick, children]);
}, [children, id, isChildExpanded, onChildClick]);

return (
<div ref={mergedRef} className={cx("accordion", className)} id={id}>
{children && renderChildElements}
</div>
);
});
return (
<div ref={mergedRef} className={cx("accordion", className)} data-testid={dataTestId} id={id}>
{children && renderChildElements}
</div>
);
}
);

Accordion.propTypes = {
/**
Expand All @@ -78,6 +93,10 @@ Accordion.propTypes = {
* Array of initial expanded indexes
*/
defaultIndex: PropTypes.array,
/**
* Unique TestId - can be used as Selector for integration tests and other needs (tracking, etc.)
*/
"data-testid": PropTypes.string,
/**
* The value of the expandable section
*/
Expand All @@ -89,6 +108,7 @@ Accordion.defaultProps = {
id: undefined,
allowMultiple: false,
children: null,
"data-testid": COMPONENT_ID,
defaultIndex: []
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Accordion from "../Accordion";
import AccordionItem from "../../AccordionItem/AccordionItem";
import { ArgsTable, Story, Canvas, Meta } from "@storybook/addon-docs";
import { createComponentTemplate } from "../../../../storybook/functions/create-component-story";
import Checkbox from "../../../Checkbox/Checkbox.js";
import { WIZARD, BREADCRUBMS, STEPPER } from "../../../../storybook/components/related-components/component-description-map";
import "./accordion.stories.scss";
import { accordionSingleActivePlaySuite } from "../__tests__/accordion.interactions"

<Meta
title="Data display/Accordion"
Expand Down Expand Up @@ -80,7 +80,7 @@ Each section can be expanded without closing the others
### Single active
Only one section can be open at the time
<Canvas>
<Story name="Single active">
<Story name="Single active" play={accordionSingleActivePlaySuite}>
<Accordion className="monday-storybook-accordion_small-wrapepr" defaultIndex={[1]}>
<AccordionItem title="Notifications"><div className="monday-storybook-accordion_small-box" /></AccordionItem>
<AccordionItem title="Setting"><div className="monday-storybook-accordion_small-box" /></AccordionItem>
Expand Down
Loading

0 comments on commit 6b884b8

Please sign in to comment.