Skip to content

Commit

Permalink
feat: [ActionPanel] add visuallyHidden prop
Browse files Browse the repository at this point in the history
  • Loading branch information
renrizzolo committed Sep 2, 2022
1 parent 28d7d83 commit fec4f37
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 8 deletions.
6 changes: 6 additions & 0 deletions src/components/ActionPanel/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export interface ActionPanelProps {
closeIcon?: React.ReactNode;
isModal?: boolean;
cancelText?: string;
/**
* Hides the modal with css, but keeps it mounted.
* This should only be used if you need to launch an ActionPanel
* from within another ActionPanel.
*/
visuallyHidden?: boolean;
dts?: string;
}

Expand Down
36 changes: 30 additions & 6 deletions src/components/ActionPanel/index.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
import React from 'react';
import ReactDOM from 'react-dom';
import { expandDts } from '../../lib/utils';
import Button from '../Button';
import './styles.css';

const ActionPanel = React.forwardRef((props, ref) => {
const { title, className, size, onClose, children, actionButton, isModal, closeIcon, cancelText, dts } = props;
const {
title,
className,
size,
onClose,
children,
visuallyHidden,
actionButton,
isModal,
closeIcon,
cancelText,
dts,
} = props;

const addBodyClass = (classname) => document.body.classList.add(classname);
const removeBodyClass = (classname) => document.body.classList.remove(classname);

useEffect(() => {
React.useLayoutEffect(() => {
if (isModal) addBodyClass('modal-open');

return () => {
if (visuallyHidden) return;
if (isModal) removeBodyClass('modal-open');
};
}, [isModal]);
}, [isModal, visuallyHidden]);

const actionPanel = (
<div ref={ref}>
<div className={isModal ? 'aui--action-panel-backdrop' : 'hide'} />
<div
className={classNames(isModal ? 'aui--action-panel-backdrop' : 'hide', { 'visually-hidden': visuallyHidden })}
/>
<div
data-testid="action-panel-modal-wrapper"
className={classNames('aui--action-panel-wrapper', { 'aui--action-panel-modal-wrapper': isModal })}
className={classNames('aui--action-panel-wrapper', {
'aui--action-panel-modal-wrapper': isModal,
'visually-hidden': visuallyHidden,
})}
>
<div
data-testid="action-panel-wrapper"
Expand Down Expand Up @@ -78,6 +96,12 @@ ActionPanel.propTypes = {
closeIcon: PropTypes.node,
isModal: PropTypes.bool,
cancelText: PropTypes.string,
/**
* Hides the modal with css, but keeps it mounted.
* This should only be used if you need to launch an ActionPanel
* from within another ActionPanel.
*/
visuallyHidden: PropTypes.bool,
dts: PropTypes.string,
};

Expand Down
6 changes: 6 additions & 0 deletions src/components/ActionPanel/index.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ describe('<ActionPanel />', () => {
expect(document.body).not.toHaveClass('modal-open');
});

it('should hide the modal with the visuallyHidden prop', () => {
const { getByTestId } = render(<ActionPanel {...makeProps({ isModal: true, visuallyHidden: true })} />);

expect(getByTestId('action-panel-modal-wrapper')).toHaveClass('visually-hidden');
});

it('should render a user specified text on the cancel button', () => {
let wrapper;
act(() => {
Expand Down
13 changes: 11 additions & 2 deletions src/components/ActionPanel/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,12 @@
background-color: #838383;
width: 100%;
opacity: 0.8;
animation: fadein 0.2s;
animation: fadein 200ms ease-out;

&.visually-hidden {
opacity: 0;
transition: opacity 200ms ease-in;
}
}

.aui--action-panel-modal-wrapper {
Expand All @@ -159,5 +164,9 @@
align-content: center;
top: 0;
left: 0;
animation: fadein 0.2s;
animation: fadein 200ms;

&.visually-hidden {
visibility: hidden;
}
}
65 changes: 65 additions & 0 deletions www/containers/props.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@
"computed": false
}
},
"visuallyHidden": {
"type": {
"name": "bool"
},
"required": false,
"description": "Hides the modal with css, but keeps it mounted.\nThis should only be used if you need to launch an ActionPanel\nfrom within another ActionPanel."
},
"dts": {
"type": {
"name": "string"
Expand Down Expand Up @@ -4383,12 +4390,70 @@
"computed": false
}
},
"hideTitle": {
"type": {
"name": "bool"
},
"required": false,
"description": "Hides the title",
"defaultValue": {
"value": "false",
"computed": false
}
},
"onChange": {
"type": {
"name": "func"
},
"required": true,
"description": ""
},
"searchOnEnter": {
"type": {
"name": "bool"
},
"required": false,
"description": "Determines whether onSearch() will be fired on ENTER key press (Default behaviour is to fire onSearch() when the input changes)",
"defaultValue": {
"value": "false",
"computed": false
}
},
"onSearch": {
"type": {
"name": "func"
},
"required": false,
"description": ""
},
"onClear": {
"type": {
"name": "func"
},
"required": false,
"description": "",
"defaultValue": {
"value": "_.noOp",
"computed": true
}
},
"showSearchButton": {
"type": {
"name": "bool"
},
"required": false,
"description": "",
"defaultValue": {
"value": "true",
"computed": false
}
},
"footerText": {
"type": {
"name": "string"
},
"required": false,
"description": ""
}
}
}
Expand Down
86 changes: 86 additions & 0 deletions www/examples/ActionPanel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,91 @@ class Example extends React.PureComponent {
render(Example);
```
## Nested Action Panels
In rare cases, a component in a modal triggers its own modal.
Use `visuallyHidden` to hide the parent modal when opening the child.
```jsx live=true
class Example extends React.PureComponent {
constructor() {
super();
this.state = {
showActionPanel: false,
showActionPanel2: false,
};
this.toggleActionPanel = this.toggleActionPanel.bind(this);
this.toggleActionPanel2 = this.toggleActionPanel2.bind(this);
}
toggleActionPanel() {
this.setState({ showActionPanel: !this.state.showActionPanel });
}
toggleActionPanel2() {
this.setState({ showActionPanel2: !this.state.showActionPanel2 });
}
render() {
return (
<React.Fragment>
<Button onClick={this.toggleActionPanel}>Action Panel as a modal</Button>
{this.state.showActionPanel && (
<ActionPanel
title="Action Panel"
size="small"
onClose={this.toggleActionPanel}
visuallyHidden={this.state.showActionPanel2}
actionButton={<Button onClick={this.toggleActionPanel}>Save</Button>}
isModal
children={
<div>
Native mammals include the dingoes or wild dogs, numbats, quolls, and Tasmanian devils. Dingoes are the
largest carnivorous mammals that populate the wilds of mainland Australia. But the smaller numbats and
Tasmanian devils, which are house cat-like size can be seen only in wildlife parks. You can also spot
them in the wilds of Tasmania.
<br />
<br />
<div>
<Button onClick={this.toggleActionPanel2}>Action Panel 2 as a modal</Button>
</div>
{this.state.showActionPanel2 && (
<ActionPanel
title="Action Panel 2"
size="medium"
onClose={this.toggleActionPanel2}
cancelText="Back"
actionButton={
<Button
onClick={() => {
this.toggleActionPanel2();
this.toggleActionPanel();
}}
>
Done
</Button>
}
isModal
children={
<div>
Native mammals include the dingoes or wild dogs, numbats, quolls, and Tasmanian devils. Dingoes
are the largest carnivorous mammals that populate the wilds of mainland Australia. But the
smaller numbats and Tasmanian devils, which are house cat-like size can be seen only in wildlife
parks. You can also spot them in the wilds of Tasmania.
</div>
}
/>
)}
</div>
}
/>
)}
</React.Fragment>
);
}
}
render(Example);
```
<DesignNotes>Action panel can be used as a modal or a information display panel.</DesignNotes>
<Props componentName="ActionPanel" />

0 comments on commit fec4f37

Please sign in to comment.