Skip to content

Commit

Permalink
ModuleExpandable: added external collapsing control (expandedIndex an…
Browse files Browse the repository at this point in the history
…d onExpandedChange) (#1323)

ModuleExpandable: added external collapsing control (expandedIndex and onExpandedChange)
with Tests and Docs examples

![Kapture 2021-01-15 at 16 11 23](https://user-images.githubusercontent.com/10593890/104779055-6810e200-574c-11eb-9cbe-27e7e9cb28bd.gif)

<!--
What is the purpose of this PR?
added external control extExpandedId and setExtExpandedId for ModuleExpandable component
added tests
updated docs

* What is the context surrounding this PR? Include links if possible.
We need external control to expand/collapse ModuleExpandable programmatically.
* What kind of feedback do you want?
* Have you [formatted the PR title](https://github.com/pinterest/gestalt/#releasing)? `ComponentName: Description`
-->
<!--
How can reviewers verify this is good to merge?

* Is it tested? Y
* Is it accessible? Y
* Is it documented? Y
* Have you involved other stakeholders (such as a Pinterest Designer)?
-->
  • Loading branch information
galaxy0101 committed Jan 20, 2021
1 parent f43a787 commit a157e3a
Show file tree
Hide file tree
Showing 7 changed files with 432 additions and 8 deletions.
89 changes: 86 additions & 3 deletions docs/src/Module.doc.js
Expand Up @@ -96,6 +96,22 @@ card(
description:
'Label used to communicate to screen readers which module will be collapsed when interacting with the title button. Should be something clear, like "Collapse Security Policies Module"',
},
{
name: 'expandedIndex',
type: '?number',
required: false,
description: [
'The 0-based index indicating the item that should currently be expanded. This must be updated via onExpandedChange to ensure the correct item is expanded.',
],
},
{
name: 'onExpandedChange',
type: '(?number) => void',
required: false,
description: [
'This callback is executed whenever any module item is expanded or collapsed. It receives the index of the currently expanded module, or null if none are expanded.',
],
},
{
name: 'items',
href: 'expandable-items',
Expand Down Expand Up @@ -204,7 +220,7 @@ card(
defaultCode={`
function ModuleExample1() {
return (
<Box maxWidth={800} padding={2} column={12}>
<Box maxWidth={800} padding={2} column={12} >
<Module.Expandable
id="ModuleExample - default"
accessibilityExpandLabel="Expand the module"
Expand All @@ -231,7 +247,7 @@ card(
defaultCode={`
function ModuleExample2() {
return (
<Box maxWidth={800} padding={2} column={12}>
<Box maxWidth={800} padding={2} column={12} >
<Module.Expandable
id="ModuleExample2"
accessibilityExpandLabel="Expand the module"
Expand Down Expand Up @@ -267,7 +283,7 @@ card(
defaultCode={`
function ModuleExample3() {
return (
<Box maxWidth={800} padding={2} column={12}>
<Box maxWidth={800} padding={2} column={12} >
<Module.Expandable
id="ModuleExample3"
accessibilityExpandLabel="Expand the module"
Expand Down Expand Up @@ -325,4 +341,71 @@ function ModuleExample4() {
/>
);

card(
<Example
name="Example with external control"
defaultCode={`
function ModuleExample5() {
const [extExpandedId, setExtExpandedId] = React.useState(null);
const mapIds = {
'first-0': 0,
'first-1': 1,
'second-0': 0,
'second-1': 1,
}
return (
<Box maxWidth={800} padding={2} column={12}>
<Flex direction='column' gap={3}>
<Box padding={1} borderStyle='sm'>
<Text>Step 1</Text>
<Module.Expandable
id="ModuleExampleStep1"
accessibilityExpandLabel="Expand the module"
accessibilityCollapseLabel="Collapse the module"
expandedIndex={extExpandedId && extExpandedId.startsWith('first') && mapIds[extExpandedId]}
onExpandedChange={(index) => setExtExpandedId(Number.isFinite(index) ? \`first-$\{index}\`: index)}
items={[
{
title: 'Title1',
summary: ['summary1'],
children: <Text size="md">Children1</Text>,
},
{
title: 'Title2',
summary: ['summary2'],
children: <Text size="md">Children2</Text>,
},
]}
/>
</Box>
<Box padding={1} borderStyle='sm'>
<Text>Step 2</Text>
<Module.Expandable
id="ModuleExampleStep2"
accessibilityExpandLabel="Expand the module"
accessibilityCollapseLabel="Collapse the module"
expandedIndex={extExpandedId && extExpandedId.startsWith('second') && mapIds[extExpandedId]}
onExpandedChange={(index) => setExtExpandedId(Number.isFinite(index) ? \`second-$\{index}\`: index)}
items={[
{
title: 'Title1',
summary: ['summary1'],
children: <Text size="md">Children1</Text>,
},
{
title: 'Title2',
summary: ['summary2'],
children: <Text size="md">Children2</Text>,
},
]}
/>
</Box>
</Flex>
</Box>
);
}
`}
/>
);

export default cards;
2 changes: 2 additions & 0 deletions packages/gestalt/src/ModuleExpandable.flowtest.js
Expand Up @@ -7,6 +7,8 @@ const Valid = (
id="uniqueTestID"
accessibilityExpandLabel="click to expand"
accessibilityCollapseLabel="click to collapse"
expandedIndex={0}
onExpandedChange={() => {}}
items={[
{
title: 'Title',
Expand Down
21 changes: 16 additions & 5 deletions packages/gestalt/src/ModuleExpandable.js
@@ -1,5 +1,5 @@
// @flow strict
import React, { useState, type Node } from 'react';
import React, { useState, useEffect, type Node } from 'react';
import Box from './Box.js';
import Divider from './Divider.js';
import ModuleExpandableItem from './ModuleExpandableItem.js';
Expand All @@ -9,9 +9,17 @@ export default function ModuleExpandable({
id,
accessibilityExpandLabel,
accessibilityCollapseLabel,
expandedIndex,
onExpandedChange,
items,
}: ExpandableBaseProps): Node {
const [expandedId, setExpandedId] = useState(-1);
const [expandedId, setExpandedId] = useState<?number>(
Number.isFinite(expandedIndex) ? expandedIndex : null
);

useEffect(() => {
setExpandedId(Number.isFinite(expandedIndex) ? expandedIndex : null);
}, [expandedIndex, setExpandedId]);

return (
<Box rounding={2} borderStyle="shadow">
Expand All @@ -32,9 +40,12 @@ export default function ModuleExpandable({
type={type}
accessibilityExpandLabel={accessibilityExpandLabel}
accessibilityCollapseLabel={accessibilityCollapseLabel}
onModuleClicked={(isExpanded) =>
setExpandedId(isExpanded ? -1 : index)
}
onModuleClicked={(isExpanded) => {
if (onExpandedChange) {
onExpandedChange(isExpanded ? null : index);
}
setExpandedId(isExpanded ? null : index);
}}
>
{children}
</ModuleExpandableItem>
Expand Down
29 changes: 29 additions & 0 deletions packages/gestalt/src/ModuleExpandable.jsdom.test.js
Expand Up @@ -74,4 +74,33 @@ describe('ModuleExpandable', () => {
expect(screen.queryByText(/Children2/i)).toBeNull();
expect(screen.getByText(/Children3/i)).toBeInTheDocument();
});

it('should expand the module correctly with expandedId', () => {
const newProps = {
...props,
expandedIndex: 0,
onExpandedChange: jest.fn(),
};
render(<ModuleExpandable {...newProps} />);

// Item with index 0 is default to be expanded
expect(screen.getByText(/Children1/i)).toBeInTheDocument();
expect(screen.queryByText(/Children2/i)).toBeNull();
expect(screen.queryByText(/Children3/i)).toBeNull();

// Click on Item with index 0 to collapse the item
const button1 = screen.getByRole('button', {
name: /click to collapse/i,
});
fireEvent.click(button1);
expect(newProps.onExpandedChange).toHaveBeenCalledWith(null);

// Click on with index 1 to expand it
const expandButtons = screen.getAllByRole('button', {
name: /click to expand/i,
});
expect(expandButtons).toHaveLength(3);
fireEvent.click(expandButtons[1]);
expect(newProps.onExpandedChange).toHaveBeenCalledWith(1);
});
});
33 changes: 33 additions & 0 deletions packages/gestalt/src/ModuleExpandable.test.js
Expand Up @@ -55,3 +55,36 @@ describe('Module Expandable', () => {
expect(tree).toMatchSnapshot();
});
});

test('renders correctly with multiple items with expandedId', () => {
const tree = renderer
.create(
<ModuleExpandable
id="uniqueTestID"
accessibilityExpandLabel="click to expand"
accessibilityCollapseLabel="click to collapse"
expandedIndex={0}
onExpandedChange={() => {}}
items={[
{
title: 'Title1',
summary: ['summary1'],
children: 'Children1',
},
{
title: 'Title2',
summary: ['summary2'],
children: 'Children2',
},
{
title: 'Title3',
summary: ['summary3'],
children: 'Children3',
type: 'error',
},
]}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

0 comments on commit a157e3a

Please sign in to comment.