terriajs
Version:
Geospatial data visualization platform.
184 lines (166 loc) • 5.59 kB
JSX
import React from "react";
import PropTypes from "prop-types";
import createReactClass from "create-react-class";
import classNames from "classnames";
import defined from "terriajs-cesium/Source/Core/defined";
import { withTranslation } from "react-i18next";
import Styles from "./panel.scss";
import Icon from "../../../Styled/Icon";
const InnerPanel = createReactClass({
propTypes: {
/**
* A property that will be looked for on window click events - if it's set, the click event will not cause the
* panel to close.
*/
doNotCloseFlag: PropTypes.string,
/** Disable closing on loss of focus and only allow with close button */
disableCloseOnFocusLoss: PropTypes.bool,
/** Will be called when the panel has finished hiding */
onDismissed: PropTypes.func,
/** Animate as modal instead of dropdown */
showDropdownAsModal: PropTypes.bool,
/** show panel centered instead of offset toward the left */
showDropdownInCenter: PropTypes.bool,
/** Relative width to draw from */
modalWidth: PropTypes.number,
/** Theme to style components */
theme: PropTypes.object,
/** How far the caret at the top of the panel should be from its left, as CSS (so #px or #% are both valid) */
caretOffset: PropTypes.string,
/** Will be passed as "ref" to the outermost element */
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/** How far the dropdown should be offset from the left of the container. */
dropdownOffset: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element
]),
t: PropTypes.func.isRequired
},
getDefaultProps() {
return {
onDismissed: () => {}
};
},
getInitialState() {
return {};
},
componentDidMount() {
this.escKeyListener = (e) => {
if (e.keyCode === 27) {
this.close(e);
}
};
window.addEventListener("click", this.close);
window.addEventListener("keydown", this.escKeyListener, true);
setTimeout(() => this.setState({ isOpenCss: true }));
},
componentWillUnmount() {
this.cleanListeners();
},
cleanListeners() {
window.removeEventListener("click", this.close);
window.removeEventListener("keydown", this.escKeyListener, true);
},
forceClose() {
this.cleanListeners();
// If we're closing we want to immediately change the css class to cause it to animate shut, then when it's
// finished actually stop it rendering with isOpen = false
this.setState({
isOpenCss: false
});
setTimeout(() => {
this.props.onDismissed();
}, 200); // TODO: Determine when it stops animating instead of duplicating the 200ms timeout?
},
close(e) {
// Only close if this wasn't a click on an open/close button.
if (
(!this.props.doNotCloseFlag || !e[this.props.doNotCloseFlag]) &&
!this.props.disableCloseOnFocusLoss
) {
this.forceClose();
}
},
render() {
const { t } = this.props;
return (
<div
className={classNames(
// Until we break these few components out of sass, we'll use regular ol classnames
"tjs-sc-InnerPanel",
Styles.inner,
this.props.theme.inner,
{ [Styles.isOpen]: this.state.isOpenCss },
{ [Styles.showDropdownAsModal]: this.props.showDropdownAsModal },
{ [Styles.showDropdownInCenter]: this.props.showDropdownInCenter }
)}
css={`
background: ${(p) => p.theme.dark};
`}
ref={this.props.innerRef}
onClick={(e) => e.stopPropagation()}
style={{
width: this.props.modalWidth,
left: this.props.dropdownOffset,
// the modal should be right-aligned with the button
right: "0px",
transformOrigin: this.props.showDropdownInCenter
? "0 top"
: this.props.caretOffset && `${this.props.caretOffset} top`
}}
>
<button
type="button"
className={classNames(
// Until we break these few components out of sass, we'll use regular ol classnames
"tjs-sc-InnerPanelCloseButton",
Styles.innerCloseBtn,
{
[Styles.innerCloseBtnForModal]: this.props.showDropdownAsModal
}
)}
onClick={this.forceClose}
title={t("general.close")}
aria-label={t("general.close")}
showDropdownAsModal={this.props.showDropdownAsModal}
css={`
svg {
fill: ${(p) => p.theme.textLight};
}
&:hover,
&:focus {
svg {
fill: ${(p) => p.theme.colorPrimary};
}
}
${(p) =>
p.showDropdownAsModal &&
`
svg {
fill: ${p.theme.grey};
}
`}
`}
>
<Icon glyph={Icon.GLYPHS.close} />
</button>
<If
condition={
defined(this.props.caretOffset) && !this.props.showDropdownAsModal
}
>
<span
className={classNames(Styles.caret, "tjs-sc-InnerPanel__caret")}
style={{ left: this.props.caretOffset }}
css={`
background: ${(p) => p.theme.dark};
`}
/>
</If>
<div className={Styles.content}>{this.props.children}</div>
</div>
);
}
});
export default withTranslation()(InnerPanel);