react-cm-ui
Version:
React UI for Healthy Church
402 lines (358 loc) • 11.7 kB
JSX
import {
isFunction,
isUndefined,
} from 'lodash';
import ClassNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import KeyCode from '../../global/keyCode.js';
import withStyles from '../../styles/withStyles.js';
const propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
/**
* Override or extend the styles applied to Prompt.
*/
classes: PropTypes.shape({
actions: PropTypes.string,
root: PropTypes.string,
}),
className: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({}),
]),
id: PropTypes.string,
inline: PropTypes.bool,
inlineHorizontalAlign: PropTypes.oneOf(['left', 'right']),
inlineMessageColor: PropTypes.oneOf(['alert', 'success', 'warning']),
message: PropTypes.string,
onClick: PropTypes.func,
onKeyDown: PropTypes.func,
onNoClick: PropTypes.func,
onNoKeyDown: PropTypes.func,
onYesClick: PropTypes.func,
onYesKeyDown: PropTypes.func,
show: PropTypes.bool,
style: PropTypes.shape({}), // eslint-disable-line react/forbid-prop-types
theme: PropTypes.shape({
zIndex: PropTypes.shape({
modal: PropTypes.number,
}),
}),
};
const defaultProps = {
children: null,
classes: null,
className: undefined,
id: undefined,
inline: false,
inlineHorizontalAlign: 'left',
inlineMessageColor: undefined,
message: 'Are you sure?',
onClick: undefined,
onKeyDown: undefined,
onNoClick: undefined,
onNoKeyDown: undefined,
onYesClick: undefined,
onYesKeyDown: undefined,
show: undefined,
style: undefined,
theme: null,
};
const styles = ({
palette,
spacing,
typography,
zIndex,
}) => ({
actions: {},
root: {
'&.prompt-inline': {
display: 'inline-block',
'& $actions': {
alignItems: 'center',
borderRadius: 3,
boxShadow: '0 4px 4px 0 rgba(0, 0, 0, .43)',
color: palette.text.contrastText,
display: 'none',
flex: '0 1 auto',
fontSize: typography.pxToRem(14),
fontWeight: typography.fontWeightMedium,
height: 33,
position: 'absolute',
zIndex: zIndex.prompt,
},
'& .prompt': {
'&-no-btn, &-yes-btn': {
alignItems: 'center',
backgroundColor: palette.grey[500],
border: 0,
// boxShadow: `inset 0 0 0 1px ${palette.grey[600]}`,
color: palette.text.contrastText,
cursor: 'pointer',
display: 'inline-flex',
height: 33,
letterSpacing: 1,
lineHeight: 1,
outline: 'none',
overflow: 'hidden',
padding: [[0, spacing(1)]],
position: 'relative',
textAlign: 'center',
textDecoration: 'none',
textTransform: 'capitalize',
transition: 'background-color 125ms linear, color 125ms linear, opacity 250ms ease-out',
verticalAlign: 'top',
'&:focus': {
boxShadow: `0 0 0 1px ${palette.active.primary}`,
},
'&:hover': {
backgroundColor: palette.active.primary,
},
},
'&-no-btn': {
borderTopRightRadius: 3,
borderBottomRightRadius: 3,
'&::before': {
backgroundColor: palette.grey[600],
content: '""',
height: 33,
left: 0,
position: 'absolute',
top: 0,
width: 1,
},
},
},
},
},
});
class Prompt extends React.Component {
constructor(props) {
super(props);
this.state = {
show: props.show || false,
inlineVerticalAlign: 0,
};
this.onClick = this.onClick.bind(this);
this.onChildKeyDown = this.onChildKeyDown.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onNoClick = this.onNoClick.bind(this);
this.onNoKeyDown = this.onNoKeyDown.bind(this);
this.onYesClick = this.onYesClick.bind(this);
this.onYesKeyDown = this.onYesKeyDown.bind(this);
this.noButtonRef = React.createRef();
}
componentDidMount() {
const { inline } = this.props;
if (inline) {
this.findInlineVerticalPosition();
}
}
componentDidUpdate(prevProps) {
const {
show: showProp,
} = this.props;
const {
show: prevShowProp,
} = prevProps;
const {
show: showState,
} = this.state;
const {
show: prevShowState,
} = prevProps;
if (showProp !== prevShowProp) {
this.setState({
show: showProp,
}, () => {
this.focusNoButton();
});
}
if (showState && !prevShowState) {
this.focusNoButton();
}
}
onClick(option) {
const { onClick } = this.props;
const { show } = this.state;
if (show) { return; }
if (!isUndefined(onClick)) {
onClick(option);
} else {
this.setState({ show: true });
}
}
onChildKeyDown(event) {
const {
onKeyDown,
} = this.props;
if (isFunction(onKeyDown)) {
onKeyDown(event);
} else if (event.keyCode === KeyCode.Enter) {
this.onClick();
}
}
onMouseDown(event) {
event.preventDefault();
}
onNoClick(event) {
const { onNoClick } = this.props;
if (!isUndefined(onNoClick)) {
onNoClick(event);
} else {
this.setState({ show: false });
}
}
onNoKeyDown(event) {
const {
onNoKeyDown,
} = this.props;
if (isFunction(onNoKeyDown)) {
onNoKeyDown(event);
}
}
onYesClick(event) {
const { onYesClick } = this.props;
if (!isUndefined(onYesClick)) {
onYesClick(event);
} else {
this.setState({ show: false });
}
}
onYesKeyDown(event) {
const {
onYesKeyDown,
} = this.props;
if (isFunction(onYesKeyDown)) {
onYesKeyDown(event);
}
}
findInlineVerticalPosition() {
const childHeight = this.childrenRef.offsetHeight;
const negativeSpace = 5;
this.setState({
inlineVerticalAlign: `${childHeight + negativeSpace}px`,
});
}
focusNoButton() {
if (this.noButtonRef && this.noButtonRef.current) {
this.noButtonRef.current.focus();
}
}
render() {
const {
children,
classes,
className,
id,
inline,
inlineHorizontalAlign,
inlineMessageColor,
message,
style,
} = this.props;
const { show, inlineVerticalAlign } = this.state;
const rootClasses = ClassNames(
'ui',
'prompt',
classes.root,
className,
{
'prompt-show': show,
'prompt-inline': inline,
},
);
const messageClasses = ClassNames('prompt-message', {
'promp-message-alert': inlineMessageColor === 'alert' || children.props.color === 'alert' || children.props.buttonColor === 'alert',
'promp-message-success': inlineMessageColor === 'success' || children.props.color === 'success' || children.props.buttonColor === 'success',
'promp-message-warning': inlineMessageColor === 'warning' || children.props.color === 'warning' || children.props.buttonColor === 'warning',
});
const promptActionsStyle = {
left: !inlineHorizontalAlign || inlineHorizontalAlign === 'left' ? 0 : null,
right: inlineHorizontalAlign === 'right' ? 0 : null,
top: inlineVerticalAlign,
};
return (
<div
className={rootClasses}
id={id}
style={style}
>
{/*
* NOTE: Remove this condition for 'Dropdown' when Dropdown is
* removed from the library.
*/}
{children && children.type.name === 'Dropdown' ? (
<div
ref={(ref) => { this.childrenRef = ref; }}
>
{React.cloneElement(children, {
className: ClassNames(
'prompt-action',
(children && children.props.className) || null,
{
'prompt-action-disable': show,
},
),
buttonColor: show && children.props.buttonColor !== 'transparent' ? 'disable' : children.props.buttonColor,
onChange: this.onClick,
})}
</div>
) : (
<div
onClick={this.onClick}
onKeyDown={this.onChildKeyDown}
ref={(ref) => { this.childrenRef = ref; }}
role="button"
tabIndex={-1}
style={{
outline: 'none',
}}
>
{children}
</div>
)}
<div
className={ClassNames(
'prompt-actions',
classes.actions,
)}
style={promptActionsStyle}
>
<div
className={messageClasses}
>
{message}
</div>
<div
className="prompt-yes-btn"
onClick={this.onYesClick}
onKeyDown={this.onYesKeyDown}
onMouseDown={this.onMouseDown}
role="button"
tabIndex={0}
>
Yes
</div>
<div
className="prompt-no-btn"
onClick={this.onNoClick}
onKeyDown={this.onNoKeyDown}
onMouseDown={this.onMouseDown}
ref={this.noButtonRef}
role="button"
tabIndex={0}
>
No
</div>
</div>
</div>
);
}
}
Prompt.propTypes = propTypes;
Prompt.defaultProps = defaultProps;
export default withStyles(styles, { withTheme: true })(Prompt);