lucid-ui
Version:
A UI component library from Xandr.
219 lines • 8.15 kB
JavaScript
import _, { omit } from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { lucidClassNames } from '../../util/style-helpers';
import Overlay from '../Overlay/Overlay';
import GripperVerticalIcon from '../Icon/GripperVerticalIcon/GripperVerticalIcon';
import CloseIcon from '../Icon/CloseIcon/CloseIcon';
import DragCaptureZone from '../DragCaptureZone/DragCaptureZone';
import Button from '../Button/Button';
import { getFirst } from '../../util/component-types';
const cx = lucidClassNames.bind('&-SidePanel');
const { any, bool, func, oneOf, node, number, string, oneOfType } = PropTypes;
/* SidePanel Header **/
const SidePanelHeader = (_props) => null;
SidePanelHeader.displayName = 'SidePanel.Header';
SidePanelHeader.propName = 'Header';
SidePanelHeader.peek = {
description: `
Defines the Header content of SidePanel. If no content is defined, it will
still show the close button.
`,
};
SidePanelHeader.propTypes = {
/**
Children that will be rendered.
*/
children: node,
};
/** TODO: Remove the 'nonPassThroughs' when the component is converted to a functional component */
const nonPassThroughs = [
'children',
'className',
'Header',
'isAnimated',
'isExpanded',
'isResizeDisabled',
'onCollapse',
'onResize',
'position',
'preventBodyScroll',
'width',
'minWidth',
'maxWidth',
'topOffset',
'initialState',
];
class SidePanel extends React.Component {
constructor() {
super(...arguments);
this.state = {
isResizing: false,
width: this.props.width,
startWidth: this.props.width,
isExpanded: this.props.isExpanded,
};
this.timerId = setTimeout(() => {
return;
}, 1);
this.handleResizeStart = () => {
this.setState({
isResizing: true,
});
};
this.handleResize = ({ dX }) => {
const { startWidth } = this.state;
const position = startWidth + dX * (this.props.position === 'right' ? -1 : 1);
this.setState({
width: _.clamp(position, this.props.minWidth, this.props.maxWidth === Infinity
? window.innerWidth
: this.props.maxWidth),
});
};
this.handleResizeEnd = ({ dX }, { event }) => {
const { startWidth, width } = this.state;
this.setState({
width: startWidth + dX * (this.props.position === 'right' ? -1 : 1),
isResizing: false,
startWidth: width,
});
this.props.onResize(startWidth - dX, { props: this.props, event });
};
this.handleCollapse = ({ event, }) => {
this.props.onCollapse({ event, props: this.props });
};
}
componentDidUpdate(prevProps) {
if (prevProps.isExpanded !== this.props.isExpanded) {
this.timerId = setTimeout(() => {
this.setState({
isExpanded: this.props.isExpanded,
});
}, 1);
}
}
componentWillUnmount() {
const { preventBodyScroll } = this.props;
if (this.timerId) {
clearTimeout(this.timerId);
}
if (preventBodyScroll) {
window.document.body.style.overflow = '';
}
}
render() {
const { children, className, isAnimated, isExpanded, isResizeDisabled, position, preventBodyScroll, topOffset, ...passThroughs } = this.props;
const headerEl = getFirst(this.props, SidePanel.Header);
const headerChildren = _.get(headerEl, 'props.children');
if (preventBodyScroll) {
window.document.body.style.overflow = isExpanded ? 'hidden' : '';
}
return (React.createElement(Overlay, { className: cx('&', {
'&-is-expanded': isExpanded && this.state.isExpanded,
'&-position-left': position === 'left',
'&-position-right': position === 'right',
'&-is-animated': isAnimated,
}, className), isShown: isExpanded || this.state.isExpanded, onBackgroundClick: this.handleCollapse, onEscape: this.handleCollapse, isAnimated: isAnimated, style: {
marginTop: topOffset,
}, ...omit(passThroughs, nonPassThroughs) },
React.createElement("div", { className: cx('&-pane'), style: {
width: this.state.width,
marginTop: topOffset,
} },
headerEl && (React.createElement("div", { className: cx('&-header') },
React.createElement("div", { className: cx('&-header-inner-wrapper') },
React.createElement("div", { className: cx('&-header-content') }, headerChildren),
React.createElement(Button, { className: cx('&-header-closer-button'), kind: 'invisible', onClick: this.handleCollapse, hasOnlyIcon: true },
React.createElement(CloseIcon, { className: cx('&-header-closer'), isClickable: true, size: 14 }))))),
React.createElement("div", { className: cx('&-body') },
!isResizeDisabled && (React.createElement(DragCaptureZone, { className: cx('&-grabber'), onDragStart: this.handleResizeStart, onDrag: this.handleResize, onDragEnd: this.handleResizeEnd },
React.createElement(GripperVerticalIcon, { width: '20' }))),
React.createElement("div", { className: cx('&-content') }, children)))));
}
}
SidePanel.displayName = 'SidePanel';
SidePanel.peek = {
description: `A fixed-positioned overlay positioned on the side of the screen at full screen height. Supports variable widths resized by the user or defined as a prop. Animated collapse and expand with optional Header and closer.`,
categories: ['layout'],
};
SidePanel.propTypes = {
/**
Content of the SidePanel, but also accepts \`<SidePanel.Header>\` to define
header content.
*/
children: node,
/**
Appended to the component-specific class names set on the root element.
*/
className: string,
/**
Alternative to using \`<SidePanel.Header>\`.
*/
Header: any,
/**
Enables animated transitions during expansion and collapse.
*/
isAnimated: bool,
/**
Controls the expanded/collapsed state as a boolean prop.
*/
isExpanded: bool,
/**
When true, hides the resizer at the edge of the SidePanel.
*/
isResizeDisabled: bool,
/**
Callback triggered when user clicks the background, hits the Esc key, or
clicks the close button in the Header.
Signature: \`({ event, props }) => {}\`
*/
onCollapse: func,
/**
Callback triggered after a user resizes to a new width.
Signature: \`(width, { event, props }) => {}\`
*/
onResize: func,
/**
Controls the position on the screen.
*/
position: oneOf(['left', 'right']),
/**
When true, it will prevent scrolling in the background when \`isExpanded\`
is true. This is accomplished by setting \`document.body.style.overflow =
'hidden'\`.
*/
preventBodyScroll: bool,
/**
Sets the initial width in pixels. The actual width may change if the user
resizes it.
*/
width: number,
/**
Sets the minimum width of the Side Panel.
*/
minWidth: number,
/**
Sets the maximum width of the Side Panel.
*/
maxWidth: number,
/**
Sets the top margin for the panel. Defaults to \`0\`.
*/
topOffset: oneOfType([number, string]),
};
SidePanel.defaultProps = {
isAnimated: true,
isExpanded: true,
isResizeDisabled: false,
onCollapse: _.noop,
onResize: _.noop,
position: 'right',
preventBodyScroll: false,
topOffset: 0,
width: 500,
minWidth: 500,
maxWidth: 1200,
};
SidePanel.Header = SidePanelHeader;
export default SidePanel;
//# sourceMappingURL=SidePanel.js.map