d2-ui
Version:
229 lines (196 loc) • 6.88 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import { createClassName } from '../component-helpers/utils';
import SvgIcon from '../svg-icon/SvgIcon';
const styles = {
root: {
position: 'fixed',
top: 48,
left: 0,
right: 0,
background: 'white',
boxShadow: 'rgba(0, 0, 0, 0.2) 0px 0px 6px 3px',
transition: 'all ease-out 75ms',
zIndex: 10,
overflow: 'hidden',
boxSizing: 'border-box',
},
content: {
position: 'relative',
height: '100%',
width: '100%',
},
endFlap: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
textAlign: 'center',
transition: 'all ease-out 75ms',
},
dragHandle: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 10,
cursor: 'ns-resize',
transition: 'all ease-out 75ms',
// borderTop: '1px solid rgba(0,0,0,0.3)',
},
};
const BACKGROUND_RGB = '255,255,255';
const BACKGROUND_RGB_EDIT = '255,248,224';
export const END_FLAP_HEIGHT = 7;
/**
* The ControlBar component can be used to put an expandable horizontal bar underneath the DHIS header bar, useful for
* UI elements that apply to the entire screen.
*/
class ControlBar extends React.Component {
constructor(props) {
super(props);
this.state = {
dragging: false,
};
}
onStartDrag = () => {
this.setState({ dragging: true });
window.addEventListener('mousemove', this.onDrag);
window.addEventListener('mouseup', this.onEndDrag);
};
onDrag = (event) => {
event.preventDefault();
event.stopPropagation();
const newHeight = event.clientY - 52;
if (this.props.onChangeHeight && newHeight !== this.props.height && newHeight > 0) {
requestAnimationFrame(() => {
this.props.onChangeHeight(newHeight);
});
}
};
onEndDrag = () => {
this.setState({ dragging: false });
window.removeEventListener('mousemove', this.onDrag);
window.removeEventListener('mouseup', this.onEndDrag);
if (this.props.onEndDrag) {
this.props.onEndDrag();
}
};
getEndFlapHeight() {
return this.showDragHandle() ? END_FLAP_HEIGHT : 0;
}
showDragHandle() {
return typeof this.props.onChangeHeight === 'function';
}
renderEndFlap() {
const backgroundColor = this.props.editMode ? BACKGROUND_RGB_EDIT : BACKGROUND_RGB;
const endFlapStyle = {
...styles.endFlap,
height: this.getEndFlapHeight(),
backgroundColor: 'lightblue',
};
const dragFlapStyle = {
...styles.dragHandle,
cursor: this.props.expandable ? 'ns-resize' : 'auto',
height: this.getEndFlapHeight(),
backgroundColor: `rgb(${backgroundColor})`,
};
const props = Object.assign(
{},
this.showDragHandle() && this.props.expandable ? { onMouseDown: this.onStartDrag } : {},
);
// Disable jsx-a11y no-role rule, because what's the alternative?
/* eslint-disable jsx-a11y/no-static-element-interactions */
return (
<div className="d2-ui-control-bar-endflap" style={endFlapStyle}>
<div
className="d2-ui-control-bar-dragflap"
style={dragFlapStyle}
{...props}
>
<SvgIcon className="d2-ui-control-bar-dragflap-icon" icon={'DragHandle'} style={{ marginTop: -7, fill: 'rgba(0,0,0,0.3)' }} />
</div>
</div>
);
/* eslint-enable jsx-a11y/no-static-element-interactions */
}
render() {
const className = createClassName('d2-ui-control-bar', this.props.selector);
const contentClassName = createClassName('d2-ui-control-bar-contents', this.props.selector);
const height = Math.max(this.props.height, 0) + this.getEndFlapHeight();
const rootStyle = Object.assign(
{},
styles.root,
// Adjust height to make room for extra components
{ height },
// Set background color for edit mode
{ background: this.props.editMode ? `rgb(${BACKGROUND_RGB_EDIT})` : 'white' },
// Disable animations while dragging
this.state.dragging ? { transition: 'none' } : {},
// Make room for the end flap
{ paddingBottom: this.getEndFlapHeight() },
);
return (
<div style={rootStyle} className={className}>
<div style={styles.content} className={contentClassName}>
{this.props.children}
</div>
{this.renderEndFlap()}
</div>
);
}
}
const positiveIntegerPropValidator = (props, propName, componentName) => {
const propValue = props[propName];
if (isNaN(parseFloat(propValue)) || !isFinite(propValue) ||
!Number.isInteger(propValue) || props[propName] < 1) {
// eslint-disable-next-line max-len
return new Error(`Invalid prop \`${propName}\` with value \`${propValue}\` supplied to component \`${componentName}\`: Must be a positive integer`);
}
return undefined;
};
ControlBar.propTypes = {
/**
* The height of the control bar in number of lines. Must be a positive integer.
*/
height: positiveIntegerPropValidator,
/**
* If true, the background color of the control bar changes to indicate that edit mode is active.
*/
editMode: PropTypes.bool,
/**
* Callback function that is called when the control bar is resized.
* The callback receives one argument: The new height in pixels.
*
* If no callback is specified the control bar will not have a drag handle.
*/
onChangeHeight: PropTypes.func,
/**
* Callback function that is called when the control bar is dropped after being dragged.
* The callback receives one argument: The new height in pixels.
*
* Ignored if no "onChangeHeight" function is provided.
*/
onEndDrag: PropTypes.func,
/**
* The contents of the control bar.
*/
children: PropTypes.node.isRequired,
/**
* If set, adds a class to the element in the format d2-ui-control-bar-selector
*/
selector: PropTypes.string,
/**
* If set, the control bar is vertically expandable by dragging the end flap
*/
expandable: PropTypes.bool,
};
ControlBar.defaultProps = {
height: 32,
editMode: false,
onChangeHeight: null,
onEndDrag: null,
selector: '',
expandable: true,
};
export default ControlBar;