UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

187 lines (186 loc) 7.49 kB
import _ from 'lodash'; import React from 'react'; import PropTypes from 'react-peek/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, omitProps } from '../../util/component-types'; const cx = lucidClassNames.bind('&-SidePanel'); const { any, bool, func, oneOf, node, number, string, oneOfType } = PropTypes; 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: node ` Children that will be rendered. `, }; 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, Object.assign({ 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, } }, omitProps(passThroughs, undefined, _.keys(SidePanel.propTypes), false)), 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 = { children: node ` Content of the SidePanel, but also accepts \`<SidePanel.Header>\` to define header content. `, className: string ` Appended to the component-specific class names set on the root element. `, Header: any ` Alternative to using \`<SidePanel.Header>\`. `, isAnimated: bool ` Enables animated transitions during expansion and collapse. `, isExpanded: bool ` Controls the expanded/collapsed state as a boolean prop. `, isResizeDisabled: bool ` When true, hides the resizer at the edge of the SidePanel. `, onCollapse: func ` Callback triggered when user clicks the background, hits the Esc key, or clicks the close button in the Header. Signature: \`({ event, props }) => {}\` `, onResize: func ` Callback triggered after a user resizes to a new width. Signature: \`(width, { event, props }) => {}\` `, position: oneOf(['left', 'right']) ` Controls the position on the screen. `, preventBodyScroll: bool ` When true, it will prevent scrolling in the background when \`isExpanded\` is true. This is accomplished by setting \`document.body.style.overflow = 'hidden'\`. `, width: number ` Sets the initial width in pixels. The actual width may change if the user resizes it. `, minWidth: number ` Sets the minimum width of the Side Panel. `, maxWidth: number ` Sets the maximum width of the Side Panel. `, topOffset: oneOfType([number, string]) ` Sets the top margin for the panel. Defaults to \`0\`. `, }; SidePanel.defaultProps = { isAnimated: true, isExpanded: true, isResizeDisabled: false, onCollapse: _.noop, onResize: _.noop, position: 'right', preventBodyScroll: false, topOffset: 0, width: 240, minWidth: -Infinity, maxWidth: Infinity, }; SidePanel.Header = SidePanelHeader; export default SidePanel;