UNPKG

revenge-react-components

Version:

react-components for revenge https://github.com/buildo/revenge

204 lines (181 loc) 6.11 kB
import React from 'react'; import cx from 'classnames'; import { props, t, skinnable, pure } from 'revenge'; import PanelHeader from './PanelHeader'; import { Props as panelMenuProps } from './PanelMenu'; import capitalize from 'lodash/string/capitalize'; import { LoadingSpinner, FlexView } from 'buildo-react-components'; import './panel.scss'; export const Props = { type: t.enums.of(['docked-top', 'docked-left', 'docked-right', 'docked-bottom', 'floating']), header: t.maybe(t.struct({ collapse: t.maybe(t.struct({ direction: t.enums.of(['up', 'left', 'right', 'down']), onExpand: t.maybe(t.Func), onCollapse: t.maybe(t.Func), initiallyExpanded: t.maybe(t.Bool) })), content: t.maybe(t.ReactNode), title: t.maybe(t.Str), hideTitleWhenExpanded: t.maybe(t.Bool), menu: t.maybe(t.struct({ ...panelMenuProps })) })), loading: t.maybe(t.Bool), softLoading: t.maybe(t.Bool), softLoadingDelay: t.maybe(t.refinement(t.Num, v => v >= 0, 'NonNegativeNumber')), children: t.ReactNode, className: t.maybe(t.Str), clearMargin: t.maybe(t.enums.of(['top', 'left', 'right', 'bottom'])), style: t.maybe(t.Obj) }; @pure @skinnable() @props(Props) export default class Panel extends React.Component { constructor(props) { super(props); this.state = { isExpanded: props.header && props.header.collapse && typeof props.header.collapse.initiallyExpanded !== 'undefined' ? props.header.collapse.initiallyExpanded : true }; } static defaultProps = { style: {}, loading: false, softLoading: false, softLoadingDelay: 0 } _softLoadingActive = false; _softLoadingTimer = null; updateSoftLoadingState({ softLoading: prevSoftLoading }) { const { softLoading, softLoadingDelay } = this.props; if (!softLoading) { if (this._softLoadingTimer) { clearTimeout(this._softLoadingTimer); } this._softLoadingActive = false; if (prevSoftLoading) { this.forceUpdate(); } } else if (!prevSoftLoading && softLoading && softLoadingDelay > 0) { // delay === 0 is handled without timer this._softLoadingTimer = setTimeout(() => { this._softLoadingActive = true; this.forceUpdate(); }, softLoadingDelay); } } isSoftLoading({ softLoading, softLoadingDelay }) { return this._softLoadingActive || (softLoading && softLoadingDelay === 0) // optimization for the default delay === 0; } componentDidMount() { this.updateSoftLoadingState({ softLoading: false }); } componentDidUpdate(oldProps) { this.updateSoftLoadingState(oldProps); } componentWillUnmount() { if (this._softLoadingTimer) { clearTimeout(this._softLoadingTimer); } } notifyParent = () => { const { header } = this.props; if (header && header.collapse) { if (this.state.isExpanded && header.collapse.onExpand) { header.collapse.onExpand(); } else if (header.collapse.onCollapse) { header.collapse.onCollapse(); } } } toggleExpanded = () => { this.setState( { isExpanded: !this.state.isExpanded }, this.notifyParent ); } getStyle = () => { const style = {}; if (this.props.clearMargin) { const marginToClear = `margin${capitalize(this.props.clearMargin)}`; style[marginToClear] = 0; } return { ...this.props.style, ...style }; } getLocals() { const panelState = this.state.isExpanded ? 'expanded' : 'collapsed'; const { header, children, loading, softLoading, softLoadingDelay, type, className } = this.props; const collapsable = header && header.collapse; const directionClass = collapsable ? ('collapse-' + header.collapse.direction) : ''; const verticalDirection = collapsable && (collapsable.direction === 'up' || collapsable.direction === 'down'); return { header, className: cx('panel', type, { collapsable }, directionClass, panelState, className), style: this.getStyle(), isExpanded: this.state.isExpanded, toggleExpanded: this.toggleExpanded, verticalDirection, children, loading, softLoading: this.isSoftLoading({ softLoading, softLoadingDelay }) }; } templateSoftLoading = ({ softLoading, isExpanded }) => { return softLoading && isExpanded ? ( <div className='panel-soft-loader'> <div className='loader gradient'></div> <div className='loader'></div> </div> ) : null; } templateHeader = ({ header, isExpanded, toggleExpanded }) => { return ( header ? <PanelHeader title={header.hideTitleWhenExpanded && isExpanded ? undefined : header.title} content={header.content} menu={header.menu} collapse={header.collapse ? { direction: header.collapse.direction, isExpanded: isExpanded, onToggleExpanded: toggleExpanded } : null} /> : null ); } templateExpandedContent = ({ children, loading }) => { return ( <FlexView className='panel-content' column grow style={{ position: 'relative' }}> {children} {loading && <LoadingSpinner />} </FlexView> ); } templateCollapsedContent = ({ header, verticalDirection }) => { return ( (!verticalDirection && header && header.title) ? <FlexView className='panel-content' column grow> <FlexView grow className='panel-content-title'> {header.title} </FlexView> </FlexView> : null ); } template({ header, children, loading, softLoading, className, style, isExpanded, toggleExpanded, verticalDirection }) { return ( <FlexView className={className} grow style={style} column> {this.templateSoftLoading({ softLoading, isExpanded })} {this.templateHeader({ header, isExpanded, toggleExpanded })} {isExpanded ? this.templateExpandedContent({ children, loading }) : this.templateCollapsedContent({ header, verticalDirection }) } </FlexView> ); } }