From d74a0c7a5aec1cbfa85b985c16913f1067e1517b Mon Sep 17 00:00:00 2001 From: Soumya Sunny Date: Fri, 13 Nov 2020 17:02:59 +0530 Subject: [PATCH] fix: accordion as a group --- .../molecules/Accordion/Accordion.js | 73 +++++++++-- .../molecules/Accordion/Accordion.stories.mdx | 6 +- .../molecules/Accordion/Accordion.style.js | 3 + .../Accordion/tests/Accordion.test.js | 39 ++++-- .../__snapshots__/Accordion.test.js.snap | 113 +++++++++++++++++- 5 files changed, 210 insertions(+), 24 deletions(-) diff --git a/lib/components/molecules/Accordion/Accordion.js b/lib/components/molecules/Accordion/Accordion.js index 00f0258e1..1893bfea9 100644 --- a/lib/components/molecules/Accordion/Accordion.js +++ b/lib/components/molecules/Accordion/Accordion.js @@ -3,23 +3,32 @@ * Accordion * */ -import React, { useState } from 'react'; +import React, { useState, useContext, useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import classnames from 'classnames'; import styles from './Accordion.style'; +const AccordionContext = React.createContext({}); + const AccordionHeader = ({ children, className, ariaLabel, accordionPanelId, isExpanded, - toggleExpanded, + disabled, }) => { - const handleClick = () => toggleExpanded(!isExpanded); - const handleOnKeyPress = (event) => event.keyCode === 13 && toggleExpanded(!isExpanded); + const { onToggle } = useContext(AccordionContext); + + const handleClick = (event) => { + if (!disabled) { + onToggle(accordionPanelId); + } + event.preventDefault(); + }; + const handleOnKeyPress = (event) => event.keyCode === 13 && onToggle(accordionPanelId); return ( {children} @@ -42,32 +53,53 @@ AccordionHeader.propTypes = { ariaLabel: PropTypes.string, accordionPanelId: PropTypes.string.isRequired, isExpanded: PropTypes.bool.isRequired, - toggleExpanded: PropTypes.func.isRequired, + disabled: PropTypes.bool, }; AccordionHeader.defaultProps = { className: 'accordion-header', ariaLabel: 'Accordion Header', + disabled: false, }; /** * AccordionPanel * */ -const AccordionPanel = ({ className, children, id, renderPanelBody }) => { - const [isExpanded, toggleExpanded] = useState(false); +export const AccordionPanel = ({ + className, + children, + id, + renderPanelBody, + defaultExpanded, + disabled, +}) => { + const { activeId, setActiveId } = useContext(AccordionContext); + + useEffect(() => { + if (defaultExpanded) { + setActiveId(id); + } + }, [defaultExpanded]); + + const isExpanded = id === activeId; const childrenWithHeaderProps = React.Children.map(children, (accordionHeader) => { if (accordionHeader.type.displayName === AccordionHeader.displayName) return React.cloneElement(accordionHeader, { accordionPanelId: id, - toggleExpanded, isExpanded, + disabled, }); return false; }); return ( -
+
{childrenWithHeaderProps} {renderPanelBody && (
@@ -83,10 +115,14 @@ AccordionPanel.propTypes = { children: PropTypes.node.isRequired, id: PropTypes.string.isRequired, renderPanelBody: PropTypes.func.isRequired, + defaultExpanded: PropTypes.bool, + disabled: PropTypes.bool, }; AccordionPanel.defaultProps = { className: 'panel', + defaultExpanded: false, + disabled: false, }; AccordionPanel.Header = AccordionHeader; @@ -94,7 +130,24 @@ AccordionPanel.Header = AccordionHeader; /** * Accordion */ -const Accordion = ({ className, children }) =>
{children}
; +const Accordion = ({ className, children }) => { + const [activeId, setActiveId] = useState(''); + + const onToggle = useCallback((id) => { + setActiveId(id); + }); + + const context = { + activeId, + setActiveId, + onToggle, + }; + return ( + +
{children}
+
+ ); +}; Accordion.propTypes = { className: PropTypes.string, diff --git a/lib/components/molecules/Accordion/Accordion.stories.mdx b/lib/components/molecules/Accordion/Accordion.stories.mdx index 000c0a547..08f1b5f52 100644 --- a/lib/components/molecules/Accordion/Accordion.stories.mdx +++ b/lib/components/molecules/Accordion/Accordion.stories.mdx @@ -1,7 +1,7 @@ import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'; import Para from '../../atoms/Para'; import Heading from '../../atoms/Heading'; -import Accordion, {AccordionVanilla} from '.'; +import Accordion, {AccordionVanilla,AccordionPanel} from '.'; @@ -27,7 +27,7 @@ import Accordion, {AccordionVanilla} from '.'; - ( The purpose of lorem ipsum is to create a natural looking block of text (sentence, @@ -51,3 +51,5 @@ import Accordion, {AccordionVanilla} from '.'; +#### Accordion Panel + diff --git a/lib/components/molecules/Accordion/Accordion.style.js b/lib/components/molecules/Accordion/Accordion.style.js index 0d5b2d451..76409f3d8 100644 --- a/lib/components/molecules/Accordion/Accordion.style.js +++ b/lib/components/molecules/Accordion/Accordion.style.js @@ -9,6 +9,9 @@ export default css` summary { cursor: pointer; padding: 20px 0; + &[disabled] { + cursor: not-allowed; + } } .accordion-panel-content { display: none; diff --git a/lib/components/molecules/Accordion/tests/Accordion.test.js b/lib/components/molecules/Accordion/tests/Accordion.test.js index d1b943216..45a1979f3 100644 --- a/lib/components/molecules/Accordion/tests/Accordion.test.js +++ b/lib/components/molecules/Accordion/tests/Accordion.test.js @@ -8,7 +8,13 @@ describe('', () => { beforeEach(() => { AccordionComponent = mount( - +

test

}> + +
+

test 2

}> + +
+

test 2

}>
@@ -19,23 +25,38 @@ describe('', () => { expect(AccordionComponent.exists()).toBe(true); }); - test('should render
', () => { - expect(AccordionComponent.find('div').length).toEqual(1); - }); - test('should render details', () => { - expect(AccordionComponent.find('details').length).toEqual(1); + expect(AccordionComponent.find('details').length).toEqual(3); }); test('should render summary', () => { - expect(AccordionComponent.find('summary').length).toEqual(1); + expect(AccordionComponent.find('summary').length).toEqual(3); + }); + + test('should open the first panel by default', () => { + expect(AccordionComponent.find('summary').first().props()).toHaveProperty( + 'aria-expanded', + true + ); + expect(AccordionComponent.find('summary').at(1).props()).toHaveProperty('aria-expanded', false); }); test('panel opens on click of header', () => { - AccordionComponent.find('summary').simulate('click'); - expect(AccordionComponent.find('summary').props()).toHaveProperty('aria-expanded', true); + AccordionComponent.find('summary').at(1).simulate('click'); + expect(AccordionComponent.find('summary').first().props()).toHaveProperty( + 'aria-expanded', + false + ); + expect(AccordionComponent.find('summary').at(1).props()).toHaveProperty('aria-expanded', true); }); + test('should not open the disabled panel', () => { + AccordionComponent.find('summary').last().simulate('click'); + expect(AccordionComponent.find('summary').last().props()).toHaveProperty( + 'aria-expanded', + false + ); + }); test('should render correctly', () => { expect(AccordionComponent).toMatchSnapshot(); }); diff --git a/lib/components/molecules/Accordion/tests/__snapshots__/Accordion.test.js.snap b/lib/components/molecules/Accordion/tests/__snapshots__/Accordion.test.js.snap index a6048452c..1f805adda 100644 --- a/lib/components/molecules/Accordion/tests/__snapshots__/Accordion.test.js.snap +++ b/lib/components/molecules/Accordion/tests/__snapshots__/Accordion.test.js.snap @@ -3,34 +3,141 @@ exports[` should render correctly 1`] = `
+ + +
+

+ test +

+
+
+
+ +
+ +
+

+ test 2 +

+
+
+
+ +
+ + + +
+

+ test 2 +

+