Repository: grommet/grommet (JavaScript)

Issue:
Accordion Panel does not expand when rendered in a child component
<!--- Provide a general summary of the issue in the Title above -->

### Expected Behavior
The AccordionPanel should be expandable when used in a child component
<!--- Tell us what should happen -->

### Actual Behavior
When nested in a child component, the AccordionPanel does not expand after version 2.11.4 of grommet.
<!--- Tell us what happens instead -->

### URL, screen shot, or Codepen exhibiting the issue
https://codesandbox.io/s/elastic-goldstine-zfifk?file=/src/App.js
<!--
  -- Here's a Codesandbox template that serves as a nice starting point
  -- for demonstrating an issue: https://codesandbox.io/s/m7mml8l0zj
  -->

### Steps to Reproduce
See the codesandbox above.  Try to open one of the accordion panels in the first example. In the second example, you will see that if you did not have a child component and nested the AccordionPanel directly under you will see that the accordion panels expand.

Also, notice that in version 2.11.3, the accordion works (you can change the version of grommet in codesandbox). 

### Your Environment

<!--- Include as many relevant details about the environment you experienced the bug in -->

- Grommet version: 2.11.4
- Browser Name and version: Any
- Operating System and version (desktop or mobile): Any



Discussion:
Please feel free to contact me for some details as well
I am also in Grommet slack channel @Yuriy Kovalchuk
@YuriyKovalcuk & @RyanCCollins , Thanks for reporting! This issue is fixed on the stable branch, feel free to try it out by pointing your grommet package.json to "grommet": "https://github.com/grommet/grommet/tarball/stable" and let us know if it works as expected.
The fix will be officially available on our next official grommet release. Keep us posted.



Patch:
diff --git a/src/js/components/Accordion/Accordion.js b/src/js/components/Accordion/Accordion.js
index 2f0659be39d..ea4acf7005e 100644
--- a/src/js/components/Accordion/Accordion.js
+++ b/src/js/components/Accordion/Accordion.js
@@ -1,67 +1,73 @@
-import React, { Children, cloneElement, useState } from 'react';
+import React, { Children, forwardRef, useState } from 'react';
 
 import { Box } from '../Box';
 
+import { AccordionContext } from './AccordionContext';
+
 const activeAsArray = active =>
   typeof active === 'number' ? [active] : active;
 
-const Accordion = ({
-  activeIndex,
-  animate = true,
-  children,
-  multiple,
-  onActive,
-  ...rest
-}) => {
-  const [activeIndexes, setActiveIndexes] = useState([]);
-  const [stateActiveIndex, setStateActiveIndex] = useState();
+const Accordion = forwardRef(
+  (
+    { activeIndex, animate = true, children, multiple, onActive, ...rest },
+    ref,
+  ) => {
+    const [activeIndexes, setActiveIndexes] = useState([]);
+    const [stateActiveIndex, setStateActiveIndex] = useState();
 
-  // Derived state from props
-  // https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
-  const derivedActiveIndexes = activeAsArray(activeIndex) || [];
-  if (
-    (typeof activeIndex !== 'undefined' || activeIndex !== stateActiveIndex) &&
-    derivedActiveIndexes.join() !== activeIndexes.join()
-  ) {
-    setActiveIndexes(derivedActiveIndexes);
-    setStateActiveIndex(activeIndex);
-  }
+    // Derived state from props
+    // https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
+    const derivedActiveIndexes = activeAsArray(activeIndex) || [];
+    if (
+      (typeof activeIndex !== 'undefined' ||
+        activeIndex !== stateActiveIndex) &&
+      derivedActiveIndexes.join() !== activeIndexes.join()
+    ) {
+      setActiveIndexes(derivedActiveIndexes);
+      setStateActiveIndex(activeIndex);
+    }
 
-  const onPanelChange = index => {
-    let nextActiveIndexes = [...(activeIndexes || [])];
+    const onPanelChange = index => {
+      let nextActiveIndexes = [...(activeIndexes || [])];
 
-    const nextActiveIndex = nextActiveIndexes.indexOf(index);
-    if (nextActiveIndex > -1) {
-      nextActiveIndexes.splice(nextActiveIndex, 1);
-    } else if (multiple) {
-      nextActiveIndexes.push(index);
-    } else {
-      nextActiveIndexes = [index];
-    }
+      const nextActiveIndex = nextActiveIndexes.indexOf(index);
+      if (nextActiveIndex > -1) {
+        nextActiveIndexes.splice(nextActiveIndex, 1);
+      } else if (multiple) {
+        nextActiveIndexes.push(index);
+      } else {
+        nextActiveIndexes = [index];
+      }
 
-    setActiveIndexes(nextActiveIndexes);
-    if (onActive) {
-      onActive(nextActiveIndexes);
-    }
-  };
+      setActiveIndexes(nextActiveIndexes);
+      if (onActive) {
+        onActive(nextActiveIndexes);
+      }
+    };
+
+    return (
+      <Box ref={ref} role="tablist" {...rest}>
+        {Children.toArray(children)
+          .filter(child => child)
+          .map((child, index) => (
+            <AccordionContext.Provider
+              // eslint-disable-next-line react/no-array-index-key
+              key={index}
+              value={{
+                active: activeIndexes.indexOf(index) > -1,
+                animate,
+                onPanelChange: () => onPanelChange(index),
+              }}
+            >
+              {child}
+            </AccordionContext.Provider>
+          ))}
+      </Box>
+    );
+  },
+);
 
-  return (
-    <Box role="tablist" {...rest}>
-      {Children.toArray(children)
-        .filter(child => child)
-        .map((child, index) => {
-          if (child) {
-            return cloneElement(child, {
-              active: activeIndexes.indexOf(index) > -1,
-              animate,
-              onPanelChange: () => onPanelChange(index),
-            });
-          }
-          return child;
-        })}
-    </Box>
-  );
-};
+Accordion.displayName = 'Accordion';
 
 let AccordionDoc;
 if (process.env.NODE_ENV !== 'production') {
diff --git a/src/js/components/AccordionPanel/AccordionPanel.js b/src/js/components/AccordionPanel/AccordionPanel.js
index 2815d54733a..2bad5828910 100644
--- a/src/js/components/AccordionPanel/AccordionPanel.js
+++ b/src/js/components/AccordionPanel/AccordionPanel.js
@@ -8,18 +8,17 @@ import { Button } from '../Button';
 import { Collapsible } from '../Collapsible';
 import { Heading } from '../Heading';
 
+import { AccordionContext } from '../Accordion/AccordionContext';
+
 const AccordionPanel = forwardRef(
   (
     {
-      active,
-      animate,
       children,
       header,
       label,
       onClick,
       onMouseOut,
       onMouseOver,
-      onPanelChange,
       onFocus,
       onBlur,
       ...rest
@@ -27,6 +26,7 @@ const AccordionPanel = forwardRef(
     ref,
   ) => {
     const theme = useContext(ThemeContext) || defaultProps.theme;
+    const { active, animate, onPanelChange } = useContext(AccordionContext);
     const [hover, setHover] = useState(undefined);
 
     const iconColor = useMemo(


Test patch:
diff --git a/src/js/components/Accordion/__tests__/Accordion-test.js b/src/js/components/Accordion/__tests__/Accordion-test.js
index 63d7cef78c0..77020b2aa23 100644
--- a/src/js/components/Accordion/__tests__/Accordion-test.js
+++ b/src/js/components/Accordion/__tests__/Accordion-test.js
@@ -214,4 +214,28 @@ describe('Accordion', () => {
     fireEvent.mouseOut(getByText('Panel 2'));
     expect(container.firstChild).toMatchSnapshot();
   });
+
+  test('wrapped panel', () => {
+    const onActive = jest.fn();
+    const Panel = ({ index }) => (
+      <AccordionPanel label={`Panel ${index}`}>
+        Panel body {index}
+      </AccordionPanel>
+    );
+    const { getByText, container } = render(
+      <Grommet>
+        <Accordion animate={false} onActive={onActive}>
+          {[1, 2].map(index => (
+            <Panel key={index} index={index} />
+          ))}
+        </Accordion>
+      </Grommet>,
+    );
+    expect(container.firstChild).toMatchSnapshot();
+
+    fireEvent.click(getByText('Panel 1'));
+    expect(onActive).toBeCalledWith([0]);
+    expect(getByText('Panel body 1')).not.toBeNull();
+    expect(container.firstChild).toMatchSnapshot();
+  });
 });
diff --git a/src/js/components/Accordion/__tests__/__snapshots__/Accordion-test.js.snap b/src/js/components/Accordion/__tests__/__snapshots__/Accordion-test.js.snap
index 05b73a3e5da..6c2f8813118 100644
--- a/src/js/components/Accordion/__tests__/__snapshots__/Accordion-test.js.snap
+++ b/src/js/components/Accordion/__tests__/__snapshots__/Accordion-test.js.snap
@@ -4144,3 +4144,460 @@ exports[`Accordion set on hover 5`] = `
   </div>
 </div>
 `;
+
+exports[`Accordion wrapped panel 1`] = `
+.c8 {
+  display: inline-block;
+  -webkit-flex: 0 0 auto;
+  -ms-flex: 0 0 auto;
+  flex: 0 0 auto;
+  width: 24px;
+  height: 24px;
+  fill: #7D4CDB;
+  stroke: #7D4CDB;
+}
+
+.c8 g {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.c8 *:not([stroke])[fill="none"] {
+  stroke-width: 0;
+}
+
+.c8 *[stroke*="#"],
+.c8 *[STROKE*="#"] {
+  stroke: inherit;
+  fill: none;
+}
+
+.c8 *[fill-rule],
+.c8 *[FILL-RULE],
+.c8 *[fill*="#"],
+.c8 *[FILL*="#"] {
+  fill: inherit;
+  stroke: none;
+}
+
+.c1 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  box-sizing: border-box;
+  outline: none;
+  max-width: 100%;
+  min-width: 0;
+  min-height: 0;
+  -webkit-flex-direction: column;
+  -ms-flex-direction: column;
+  flex-direction: column;
+}
+
+.c2 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  box-sizing: border-box;
+  outline: none;
+  max-width: 100%;
+  min-width: 0;
+  min-height: 0;
+  -webkit-flex-direction: column;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  -webkit-flex: 0 0 auto;
+  -ms-flex: 0 0 auto;
+  flex: 0 0 auto;
+}
+
+.c4 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  box-sizing: border-box;
+  outline: none;
+  max-width: 100%;
+  -webkit-align-items: center;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  min-width: 0;
+  min-height: 0;
+  -webkit-flex-direction: row;
+  -ms-flex-direction: row;
+  flex-direction: row;
+  -webkit-box-pack: justify;
+  -webkit-justify-content: space-between;
+  -ms-flex-pack: justify;
+  justify-content: space-between;
+}
+
+.c5 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  box-sizing: border-box;
+  outline: none;
+  max-width: 100%;
+  min-width: 0;
+  min-height: 0;
+  -webkit-flex-direction: column;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  padding-left: 6px;
+  padding-right: 6px;
+}
+
+.c7 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  box-sizing: border-box;
+  outline: none;
+  max-width: 100%;
+  min-width: 0;
+  min-height: 0;
+  -webkit-flex-direction: column;
+  -ms-flex-direction: column;
+  flex-direction: column;
+  padding-left: 12px;
+  padding-right: 12px;
+}
+
+.c9 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  box-sizing: border-box;
+  outline: none;
+  max-width: 100%;
+  border-bottom: solid 1px rgba(0,0,0,0.33);
+  min-width: 0;
+  min-height: 0;
+  -webkit-flex-direction: column;
+  -ms-flex-direction: column;
+  flex-direction: column;
+}
+
+.c3 {
+  display: inline-block;
+  box-sizing: border-box;
+  cursor: pointer;
+  outline: none;
+  font: inherit;
+  -webkit-text-decoration: none;
+  text-decoration: none;
+  margin: 0;
+  background: transparent;
+  overflow: visible;
+  text-transform: none;
+  color: inherit;
+  border: none;
+  padding: 0;
+  text-align: inherit;
+}
+
+.c6 {
+  font-size: 18px;
+  line-height: 24px;
+  max-width: 432px;
+  font-weight: 600;
+}
+
+.c0 {
+  font-size: 18px;
+  line-height: 24px;
+  box-sizing: border-box;
+  -webkit-text-size-adjust: 100%;
+  -ms-text-size-adjust: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+}
+
+@media only screen and (max-width:768px) {
+  .c5 {
+    padding-left: 3px;
+    padding-right: 3px;
+  }
+}
+
+@media only screen and (max-width:768px) {
+  .c7 {
+    padding-left: 6px;
+    padding-right: 6px;
+  }
+}
+
+@media only screen and (max-width:768px) {
+  .c9 {
+    border-bottom: solid 1px rgba(0,0,0,0.33);
+  }
+}
+
+@media only screen and (max-width:768px) {
+  .c6 {
+    font-size: 18px;
+    line-height: 24px;
+    max-width: 432px;
+  }
+}
+
+<div
+  class="c0"
+>
+  <div
+    class="c1"
+    role="tablist"
+  >
+    <div
+      class="c2"
+    >
+      <button
+        aria-expanded="false"
+        aria-selected="false"
+        class="c3"
+        role="tab"
+        type="button"
+      >
+        <div
+          class="c4"
+        >
+          <div
+            class="c5"
+          >
+            <h4
+              class="c6"
+            >
+              Panel 1
+            </h4>
+          </div>
+          <div
+            class="c7"
+          >
+            <svg
+              aria-label="FormDown"
+              class="c8"
+              viewBox="0 0 24 24"
+            >
+              <polyline
+                fill="none"
+                points="18 9 12 15 6 9"
+                stroke="#000"
+                stroke-width="2"
+              />
+            </svg>
+          </div>
+        </div>
+      </button>
+      <div
+        class="c9"
+      />
+    </div>
+    <div
+      class="c2"
+    >
+      <button
+        aria-expanded="false"
+        aria-selected="false"
+        class="c3"
+        role="tab"
+        type="button"
+      >
+        <div
+          class="c4"
+        >
+          <div
+            class="c5"
+          >
+            <h4
+              class="c6"
+            >
+              Panel 2
+            </h4>
+          </div>
+          <div
+            class="c7"
+          >
+            <svg
+              aria-label="FormDown"
+              class="c8"
+              viewBox="0 0 24 24"
+            >
+              <polyline
+                fill="none"
+                points="18 9 12 15 6 9"
+                stroke="#000"
+                stroke-width="2"
+              />
+            </svg>
+          </div>
+        </div>
+      </button>
+      <div
+        class="c9"
+      />
+    </div>
+  </div>
+</div>
+`;
+
+exports[`Accordion wrapped panel 2`] = `
+<div
+  class="StyledGrommet-sc-19lkkz7-0 lcXYAf"
+>
+  <div
+    class="StyledBox-sc-13pk1d4-0 jJdKAu"
+    role="tablist"
+  >
+    <div
+      class="StyledBox-sc-13pk1d4-0 hTZIev"
+    >
+      <button
+        aria-expanded="true"
+        aria-selected="true"
+        class="StyledButton-sc-323bzc-0 dnCEuz"
+        role="tab"
+        type="button"
+      >
+        <div
+          class="StyledBox-sc-13pk1d4-0 kDANyu"
+        >
+          <div
+            class="StyledBox-sc-13pk1d4-0 ksaQUA"
+          >
+            <h4
+              class="StyledHeading-sc-1rdh4aw-0 cJdkwo"
+            >
+              Panel 1
+            </h4>
+          </div>
+          <div
+            class="StyledBox-sc-13pk1d4-0 goKxjo"
+          >
+            .c0 {
+  display: inline-block;
+  -webkit-flex: 0 0 auto;
+  -ms-flex: 0 0 auto;
+  flex: 0 0 auto;
+  width: 24px;
+  height: 24px;
+  fill: #7D4CDB;
+  stroke: #7D4CDB;
+}
+
+.c0 g {
+  fill: inherit;
+  stroke: inherit;
+}
+
+.c0 *:not([stroke])[fill="none"] {
+  stroke-width: 0;
+}
+
+.c0 *[stroke*="#"],
+.c0 *[STROKE*="#"] {
+  stroke: inherit;
+  fill: none;
+}
+
+.c0 *[fill-rule],
+.c0 *[FILL-RULE],
+.c0 *[fill*="#"],
+.c0 *[FILL*="#"] {
+  fill: inherit;
+  stroke: none;
+}
+
+@media only screen and (max-width:768px) {
+
+}
+
+@media only screen and (max-width:768px) {
+
+}
+
+@media only screen and (max-width:768px) {
+
+}
+
+@media only screen and (max-width:768px) {
+
+}
+
+<svg
+              aria-label="FormUp"
+              class="c0"
+              viewBox="0 0 24 24"
+            >
+              <polyline
+                fill="none"
+                points="18 9 12 15 6 9"
+                stroke="#000"
+                stroke-width="2"
+                transform="matrix(1 0 0 -1 0 24)"
+              />
+            </svg>
+          </div>
+        </div>
+      </button>
+      <div
+        class="StyledBox-sc-13pk1d4-0 GCqjV"
+      >
+        Panel body 
+        1
+      </div>
+    </div>
+    <div
+      class="StyledBox-sc-13pk1d4-0 hTZIev"
+    >
+      <button
+        aria-expanded="false"
+        aria-selected="false"
+        class="StyledButton-sc-323bzc-0 dnCEuz"
+        role="tab"
+        type="button"
+      >
+        <div
+          class="StyledBox-sc-13pk1d4-0 kDANyu"
+        >
+          <div
+            class="StyledBox-sc-13pk1d4-0 ksaQUA"
+          >
+            <h4
+              class="StyledHeading-sc-1rdh4aw-0 cJdkwo"
+            >
+              Panel 2
+            </h4>
+          </div>
+          <div
+            class="StyledBox-sc-13pk1d4-0 goKxjo"
+          >
+            <svg
+              aria-label="FormDown"
+              class="StyledIcon-ofa7kd-0 kbXEEj"
+              viewBox="0 0 24 24"
+            >
+              <polyline
+                fill="none"
+                points="18 9 12 15 6 9"
+                stroke="#000"
+                stroke-width="2"
+              />
+            </svg>
+          </div>
+        </div>
+      </button>
+      <div
+        class="StyledBox-sc-13pk1d4-0 GCqjV"
+      />
+    </div>
+  </div>
+</div>
+`;
diff --git a/src/js/components/__tests__/__snapshots__/components-test.js.snap b/src/js/components/__tests__/__snapshots__/components-test.js.snap
index 78fb04e732e..f77e8b00b45 100644
--- a/src/js/components/__tests__/__snapshots__/components-test.js.snap
+++ b/src/js/components/__tests__/__snapshots__/components-test.js.snap
@@ -2,7 +2,173 @@
 
 exports[`Components loads 1`] = `
 Object {
-  "Accordion": [Function],
+  "Accordion": Object {
+    "availableAt": Array [
+      Object {
+        "badge": "https://cdn-images-1.medium.com/fit/c/120/120/1*TD1P0HtIH9zF0UEH28zYtw.png",
+        "label": "Storybook",
+        "url": "https://storybook.grommet.io/?selectedKind=Accordion&full=0&addons=0&stories=1&panelRight=0",
+      },
+      Object {
+        "badge": "https://codesandbox.io/static/img/play-codesandbox.svg",
+        "label": "CodeSandbox",
+        "url": "https://codesandbox.io/s/github/grommet/grommet-sandbox?initialpath=accordion&module=%2Fsrc%2FAccordion.js",
+      },
+    ],
+    "description": "An accordion containing collapsible panels.",
+    "intrinsicElement": "div",
+    "name": "Accordion",
+    "properties": Array [
+      Object {
+        "description": "Custom label to be used by screen readers. When provided, an aria-label will
+   be added to the element.",
+        "format": "string",
+        "name": "a11yTitle",
+      },
+      Object {
+        "description": "How to align along the cross axis when contained in
+      a Box or along the column axis when contained in a Grid.",
+        "format": "start
+center
+end
+stretch",
+        "name": "alignSelf",
+      },
+      Object {
+        "description": "The name of the area to place
+    this inside a parent Grid.",
+        "format": "string",
+        "name": "gridArea",
+      },
+      Object {
+        "description": "The amount of margin around the component. An object can
+    be specified to distinguish horizontal margin, vertical margin, and
+    margin on a particular side.",
+        "format": "none
+xxsmall
+xsmall
+small
+medium
+large
+xlarge
+{
+  bottom: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string,
+  end: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string,
+  horizontal: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string,
+  left: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string,
+  right: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string,
+  start: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string,
+  top: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string,
+  vertical: 
+    xxsmall
+    xsmall
+    small
+    medium
+    large
+    xlarge
+    string
+}
+string",
+        "name": "margin",
+      },
+      Object {
+        "defaultValue": 0,
+        "description": "Active panel index. If specified, Accordion will be a controlled 
+component. This means that future panel changes will not work unless you
+subscribe to onActive function and update activeIndex accordingly.",
+        "format": "number
+[number]",
+        "name": "activeIndex",
+      },
+      Object {
+        "defaultValue": true,
+        "description": "Transition content in & out with a slide down animation.",
+        "format": "boolean",
+        "name": "animate",
+      },
+      Object {
+        "description": "Array of AccordionPanels.",
+        "format": "node",
+        "name": "children",
+      },
+      Object {
+        "description": "Function that will be called when the active index changes.
+It will always send an array with currently active panel indexes.",
+        "format": "function",
+        "name": "onActive",
+      },
+      Object {
+        "defaultValue": false,
+        "description": "Allow multiple panels to be opened at once.",
+        "format": "boolean",
+        "name": "multiple",
+      },
+      Object {
+        "defaultValue": Object {
+          "tabContents": "Tab Contents",
+        },
+        "description": "Custom messages for Tabs. Used for accessibility by screen readers.",
+        "format": "{
+  tabContents: string
+}",
+        "name": "messages",
+      },
+    ],
+    "usage": "import { Accordion, AccordionPanel } from 'grommet';
+<Accordion>
+  <AccordionPanel label='Panel 1'>...</AccordionPanel>
+  <AccordionPanel label='Panel 2'>...</AccordionPanel>
+</Accordion>",
+  },
   "AccordionPanel": Object {
     "description": "An Accordion panel.",
     "intrinsicElement": "div",