UNPKG

lucid-ui

Version:

A UI component library from Xandr.

219 lines 8.15 kB
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