UNPKG

@spaced-out/ui-design-system

Version:
182 lines (164 loc) 4.35 kB
// @flow strict import * as React from 'react'; import useMountTransition from '../../hooks/useMountTransition'; import { motionDurationNormal, motionDurationSlow, } from '../../styles/variables/_motion'; import { spaceFluid, spaceNegFluid, spaceNone, } from '../../styles/variables/_space'; import classify from '../../utils/classify'; import {Button} from '../Button'; import type {ModalProps} from '../Modal'; import {Modal} from '../Modal'; import {Truncate} from '../Truncate'; import css from './Panel.module.css'; export type PanelSize = 'small' | 'medium' | 'large'; export type PanelAnchor = 'left' | 'right'; export type PanelHeaderProps = { children?: React.Node, hideCloseBtn?: boolean, onCloseButtonClick?: ?(SyntheticEvent<HTMLElement>) => mixed, className?: string, size?: 'medium' | 'small', }; type FooterClassNames = $ReadOnly<{ wrapper?: string, actions?: string, }>; export type PanelFooterProps = { children?: React.Node, classNames?: FooterClassNames, }; export type PanelBodyProps = { children?: React.Node, className?: string, }; export type PanelProps = { ...ModalProps, allowBackgroundInteraction?: boolean, size?: PanelSize, anchor?: PanelAnchor, }; const getDefaultPanelAnimation = (anchor: PanelAnchor) => ({ // Configure both open and close durations: duration: { open: parseInt(motionDurationSlow), close: parseInt(motionDurationNormal), }, initial: { transform: `translateX(${anchor === 'right' ? spaceFluid : spaceNegFluid})`, }, open: { transform: `translateX(${spaceNone})`, }, close: { transform: `translateX(${anchor === 'right' ? spaceFluid : spaceNegFluid})`, }, }); export const PanelHeader = ({ children, hideCloseBtn, onCloseButtonClick, className, size, }: PanelHeaderProps): React.Node => ( <> {React.Children.count(children) > 0 && ( <div className={classify(css.panelHeader, className)}> <div className={css.headerContent}> <Truncate>{children}</Truncate> </div> {!hideCloseBtn && ( <Button iconLeftName="xmark" type="ghost" onClick={onCloseButtonClick} ariaLabel="Close Button" size={size} ></Button> )} </div> )} </> ); export const PanelBody: React$AbstractComponent< PanelBodyProps, HTMLDivElement, > = React.forwardRef<PanelBodyProps, HTMLDivElement>( ({children, className}: PanelBodyProps, ref): React.Node => ( <div ref={ref} className={classify(css.panelBody, className)}> {children} </div> ), ); export const PanelFooter = ({ children, classNames, }: PanelFooterProps): React.Node => ( <> {React.Children.count(children) > 0 && ( <div className={classify(css.panelFooter, classNames?.wrapper)}> <div className={classify(css.panelFooterActions, classNames?.actions)}> {children} </div> </div> )} </> ); export const Panel = ({ children, isOpen = false, size = 'medium', anchor = 'left', onClose, hideBackdrop = true, classNames, customAnimation, tapOutsideToClose = true, allowBackgroundInteraction, ...restPanelProps }: PanelProps): React.Node => { const isTransitioning = useMountTransition( isOpen, parseInt(motionDurationNormal), ); const defaultPanelAnimation = getDefaultPanelAnimation(anchor); return ( <Modal isOpen={isOpen} onClose={onClose} hideBackdrop={hideBackdrop} tapOutsideToClose={allowBackgroundInteraction ? false : tapOutsideToClose} allowBackgroundInteraction={allowBackgroundInteraction} {...restPanelProps} classNames={{ container: classify( css.panelContainer, { [css.in]: isTransitioning, [css.open]: isOpen, }, classNames?.container, ), content: classify( css.panel, css[anchor], { [css.medium]: size === 'medium', [css.small]: size === 'small', [css.large]: size === 'large', }, classNames?.content, ), backdrop: classify(classNames?.backdrop), }} customAnimation={customAnimation || defaultPanelAnimation} > {children} </Modal> ); };