import * as React from 'react' ; import styled , { css } from 'styled-components' ; import { useKeyPress , useTheme } from '../../utils/hooks' ; import { Icon } from '../Icon' ; import { H1 } from '../typography/Heading' ; import { variables } from '../../config' ; const ZERO_PADDING = [ '0px' , '0px' , '0px' , '0px' ] ; const MODAL_PADDING_TOP = [ '16px' , '35px' , '35px' , '35px' ] ; const MODAL_PADDING_TOP_TINY = [ '16px' , '24px' , '24px' , '24px' ] ; const MODAL_PADDING_BOTTOM = [ '16px' , '35px' , '35px' , '35px' ] ; const MODAL_PADDING_BOTTOM_TINY = [ '16px' , '24px' , '24px' , '24px' , ] ; const SIDE_PADDING = [ '16px' , '40px' , '40px' , '40px' ] ; const SIDE_PADDING_TINY = [ '16px' , '32px' , '32px' , '32px' ] ; const FIXED_WIDTH = [ '95vw' , '90vw' , '720px' , '720px' ] ; const FIXED_WIDTH_SMALL = [ '95vw' , '90vw' , '600px' , '600px' ] ; const FIXED_WIDTH_TINY = [ '360px' , '360px' , '360px' , '360px' ] ; const FIXED_HEIGHT = [ '90vh' , '90vh' , '620px' , '620px' ] ; const ModalOverlay = styled . div < { guidePanelSize : string ; } > `
    position: fixed;
    z-index: 10000;
    width: 100%;
    height: 100%;
    top: 0px;
    left: 0;
    background: rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(5px);
    display: flex;
    flex-direction: column;
    align-items: center;
    overflow: auto;
    justify-content: center;

    > * {
        margin-right: ${ props => props . guidePanelSize };
        transition: all 0.3s;
    }
` ; const Header = styled . div `
    margin-bottom: 25px;
` ; type ModalWindowProps = Omit < Props , 'fixedWidth' | 'fixedHeight' | 'modalPaddingBottom' | 'modalPaddingTop' | 'modalPaddingSide' > & Required < Pick < Props , 'fixedWidth' | 'fixedHeight' | 'modalPaddingBottom' | 'modalPaddingTop' | 'modalPaddingSide' > > ; const ModalWindow = styled . div < ModalWindowProps > `
    display: flex;
    flex-direction: column;
    position: relative;
    border-radius: 16px;
    text-align: center;
    overflow-x: hidden; /* retains border-radius when using background in child component */
    transition: all 0.3s;
    padding: ${ ( props ) => `${ props . modalPaddingTop [ 3 ] } ${ props . modalPaddingSide [ 3 ] } ${ props . modalPaddingBottom [ 3 ] }` };

    @media only screen and (max-width: ${ variables . SCREEN_SIZE . SM }) {
        padding: ${ ( props ) => `${ props . modalPaddingTop [ 0 ] } ${ props . modalPaddingSide [ 0 ] } ${ props . modalPaddingBottom [ 0 ] }` };
    }

    @media only screen and (min-width: ${ variables . SCREEN_SIZE . SM }) and (max-width: ${ variables . SCREEN_SIZE . MD }) {
        padding: ${ ( props ) => `${ props . modalPaddingTop [ 1 ] } ${ props . modalPaddingSide [ 1 ] } ${ props . modalPaddingBottom [ 1 ] }` };
    }

    @media only screen and (min-width: ${ variables . SCREEN_SIZE . MD }) and (max-width: ${ variables . SCREEN_SIZE . LG }) {
        padding: ${ ( props ) => `${ props . modalPaddingTop [ 2 ] } ${ props . modalPaddingSide [ 2 ] } ${ props . modalPaddingBottom [ 2 ] }` };
    }

    ${ props => ! props . noBackground && css `
            background: ${ props => props . theme . BG_WHITE };
            box-shadow: 0 10px 80px 0 {props => props.theme.BOX_SHADOW_MODAL};
        ` }

    /* if bottomBar is active we need to disable bottom padding */
    ${ props => props . bottomBar && css `
            padding-bottom: 0px;
            @media only screen and (max-width: ${ variables . SCREEN_SIZE . SM }) {
                padding-bottom: 0px;
            }
        ` }
    /* content-based width mode */
    ${ props => ! props . useFixedWidth && css `
            max-width: 95vw;
        ` }

    ${ props => ! props . useFixedHeight && css `
            max-height: 90vh;
        ` }

    /* Fixed width mode */
    ${ props => props . useFixedWidth && props . fixedWidth && css `
            /* default width is the same as for XL screens */
            width: ${ ( props ) => props . fixedWidth [ 3 ] };
            /* for smaller screens width is set based on fixedWidth prop */
            @media only screen and (max-width: ${ variables . SCREEN_SIZE . SM }) {
                width: ${ ( props ) => props . fixedWidth [ 0 ] };
            }
            @media only screen and (min-width: ${ variables . SCREEN_SIZE . SM }) and (max-width: ${ variables . SCREEN_SIZE . MD }) {
                width: ${ ( props ) => props . fixedWidth [ 1 ] };
            }
            @media only screen and (min-width: ${ variables . SCREEN_SIZE . MD }) and (max-width: ${ variables . SCREEN_SIZE . LG }) {
                width: ${ ( props ) => props . fixedWidth [ 2 ] };
            }
        ` }

    ${ props => props . useFixedHeight && props . fixedHeight && css `
            /* default height is the same as for XL screens */
            height: ${ ( props ) => props . fixedHeight [ 3 ] };
            /* for smaller screens height is set based on fixedHeight prop */
            @media only screen and (max-width: ${ variables . SCREEN_SIZE . SM }) {
                height: ${ ( props ) => props . fixedHeight [ 0 ] };
            }
            @media only screen and (min-width: ${ variables . SCREEN_SIZE . SM }) and (max-width: ${ variables . SCREEN_SIZE . MD }) {
                height: ${ ( props ) => props . fixedHeight [ 1 ] };
            }
            @media only screen and (min-width: ${ variables . SCREEN_SIZE . MD }) and (max-width: ${ variables . SCREEN_SIZE . LG }) {
                height: ${ ( props ) => props . fixedHeight [ 2 ] };
            }
        ` }
` ; interface HeadingProps { cancelable : boolean ; showHeaderBorder : boolean ; showProgressBar : boolean ; hiddenProgressBar : boolean ; noHeadingPadding : boolean ; } const Heading = styled ( H1 ) < HeadingProps > `
    display: flex;
    align-items: flex-start;
    word-break: break-word;
    padding: ${ props => ( props . noHeadingPadding ? '0px' : '28px 32px 22px 32px' ) };
    margin-bottom: ${ props => ( props . showProgressBar ? 0 : '20px' ) };
    font-weight: ${ variables . FONT_WEIGHT . MEDIUM };

    border-bottom: ${ props => props . showHeaderBorder ? `1px solid ${ props . theme . STROKE_GREY }` : 'none' };

    /* if progress bar with green bar is being showed, do not show header border (set color to white) */
    border-color: ${ props => props . showProgressBar && ! props . hiddenProgressBar ? 'transparent' : props . theme . STROKE_GREY };

    /* align content based on the 'cancelable' prop */
    text-align: ${ props => ( props . cancelable ? 'left' : 'center' ) };
    justify-content: ${ props => props . cancelable ? 'space-between' : 'center' }; /* space-between -> to move the closing button all the way to the right */

    @media only screen and (max-width: ${ variables . SCREEN_SIZE . SM }) {
        padding: 20px 22px 16px 22px;
    }
` ; const CancelIconWrapper = styled . div `
    display: inline-block;
    position: relative;
    top: auto;
    right: auto;
    align-items: center;
    margin-left: 30px;
    cursor: pointer;
` ; const ProgressBarPlaceholder = styled . div < { hiddenProgressBar : boolean ; } > `
    height: 4px;
    width: 100%;
    margin-bottom: 20px;
    background-color: ${ props => props . hiddenProgressBar ? 'transparent' : props . theme . STROKE_GREY };
` ; const GreenBar = styled . div < { width : number ; } > `
    height: 4px;
    position: relative;
    background-color: ${ props => props . theme . BG_GREEN };
    transition: all 0.5s;
    width: ${ props => `${ props . width }%` };
` ; const SidePaddingWrapper = styled . div < { sidePadding : [ string , string , string , string ] ; } > `
    /* This component applies responsive side padding to all components that inherit from this component */
    padding-left: ${ props => props . sidePadding [ 3 ] };
    padding-right: ${ props => props . sidePadding [ 3 ] };

    @media only screen and (max-width: ${ variables . SCREEN_SIZE . SM }) {
        padding-left: ${ props => props . sidePadding [ 0 ] };
        padding-right: ${ props => props . sidePadding [ 0 ] };
    }

    @media only screen and (min-width: ${ variables . SCREEN_SIZE . SM }) and (max-width: ${ variables . SCREEN_SIZE . MD }) {
        padding-left: ${ props => props . sidePadding [ 1 ] };
        padding-right: ${ props => props . sidePadding [ 1 ] };
    }
` ; const Description = styled ( SidePaddingWrapper ) `
    color: ${ props => props . theme . TYPE_LIGHT_GREY };
    font-size: ${ variables . FONT_SIZE . SMALL };
    margin-bottom: 10px;
    text-align: center;
` ; const Content = styled ( SidePaddingWrapper ) < { centerContent : boolean ; } > `
    display: flex;
    color: ${ props => props . theme . TYPE_DARK_GREY };
    flex-direction: column;
    width: 100%;
    height: 100%;
    overflow-y: auto;

    /* center the content if props.centerContent selected. For example useful for centering nested modals */
    ${ props => props . centerContent && css `
            justify-content: center;
            align-items: center;
        ` }
` ; const BottomBar = styled ( SidePaddingWrapper ) `
    display: flex;
    padding-top: 16px;
    padding-bottom: 16px;
` ; type SIZE = 'large' | 'small' | 'tiny' ; const getFixedWidth = ( size ) => { switch ( size ) { case 'large' : return FIXED_WIDTH ; case 'small' : return FIXED_WIDTH_SMALL ; case 'tiny' : return FIXED_WIDTH_TINY ; } } ; const getContentPaddingSide = ( size , noPadding , noSidePadding ) => { if ( noPadding || noSidePadding ) { return ZERO_PADDING ; } switch ( size ) { case 'large' : return SIDE_PADDING ; case 'small' : return SIDE_PADDING ; case 'tiny' : return SIDE_PADDING_TINY ; } } ; const getModalPaddingTop = ( size , heading , noPadding ) => { if ( heading || noPadding ) { return ZERO_PADDING ; } switch ( size ) { case 'large' : return MODAL_PADDING_TOP ; case 'small' : return MODAL_PADDING_TOP ; case 'tiny' : return MODAL_PADDING_TOP_TINY ; } } ; const getModalPaddingBottom = ( size , noPadding ) => { if ( noPadding ) { return ZERO_PADDING ; } switch ( size ) { case 'large' : return MODAL_PADDING_BOTTOM ; case 'small' : return MODAL_PADDING_BOTTOM ; case 'tiny' : return MODAL_PADDING_BOTTOM_TINY ; } } ; interface Props extends React . HTMLAttributes < HTMLDivElement > { children ? : React . ReactNode ; heading ? : React . ReactNode ; header ? : React . ReactNode ; description ? : React . ReactNode ; bottomBar ? : React . ReactNode ; cancelable ? : boolean ; size ? : SIZE ; useFixedWidth ? : boolean ; fixedWidth ? : [ string , string , string , string ] ; useFixedHeight ? : boolean ; fixedHeight ? : [ string , string , string , string ] ; modalPaddingTop ? : [ string , string , string , string ] ; modalPaddingBottom ? : [ string , string , string , string ] ; modalPaddingSide ? : [ string , string , string , string ] ; contentPaddingSide ? : [ string , string , string , string ] ; noPadding ? : boolean ; noHeadingPadding ? : boolean ; noSidePadding ? : boolean ; noBackground ? : boolean ; onCancel ? : ( ) => void ; showHeaderBorder ? : boolean ; hiddenProgressBar ? : boolean ; totalProgressBarSteps ? : number ; currentProgressBarStep ? : number ; centerContent ? : boolean ; guideOpen ? : boolean ; } const Modal = ( { children , heading , header , description , bottomBar , cancelable = false , onClick , onCancel , size = 'large' , noBackground = false , useFixedWidth = true , fixedWidth = getFixedWidth ( size ) , useFixedHeight = false , fixedHeight = FIXED_HEIGHT , noPadding = false , noHeadingPadding = false , noSidePadding = false , modalPaddingTop = getModalPaddingTop ( size , heading , noPadding ) , modalPaddingBottom = getModalPaddingBottom ( size , noPadding ) , modalPaddingSide = ZERO_PADDING , contentPaddingSide = getContentPaddingSide ( size , noPadding , noSidePadding ) , showHeaderBorder = true , hiddenProgressBar = false , totalProgressBarSteps , currentProgressBarStep , centerContent = false , guideOpen = false , ... rest } ) => { const escPressed = useKeyPress ( 'Escape' ) ; const theme = useTheme ( ) ; const showProgressBarPlaceholder = hiddenProgressBar || ( totalProgressBarSteps !== undefined && currentProgressBarStep !== undefined ) ; let progressBarWidth = null ; if ( ! hiddenProgressBar && totalProgressBarSteps && currentProgressBarStep ) { progressBarWidth = ( 100 / totalProgressBarSteps ) * currentProgressBarStep ; } if ( cancelable && onCancel && escPressed ) { onCancel ( ) ; } const modalWindow = ( < ModalWindow size = { size } useFixedWidth = { useFixedWidth } fixedWidth = { fixedWidth } useFixedHeight = { useFixedHeight } fixedHeight = { fixedHeight } modalPaddingTop = { modalPaddingTop } modalPaddingBottom = { modalPaddingBottom } modalPaddingSide = { modalPaddingSide } bottomBar = { bottomBar } noBackground = { noBackground } onClick = { e => { if ( onClick ) onClick ( e ) ; e . stopPropagation ( ) ; } } { ... rest } > { heading && ( < Heading cancelable = { cancelable } showHeaderBorder = { showHeaderBorder } hiddenProgressBar = { hiddenProgressBar } showProgressBar = { showProgressBarPlaceholder } noHeadingPadding = { noHeadingPadding } > { heading } { cancelable && ( < CancelIconWrapper data - test = "@modal/close-button" onClick = { onCancel } > < Icon size = { 24 } color = { theme . TYPE_DARK_GREY } icon = "CROSS" / > < /CancelIconWrapper>)} < /Heading>)} { showProgressBarPlaceholder && ( < ProgressBarPlaceholder hiddenProgressBar = { hiddenProgressBar } > { } { ! hiddenProgressBar && progressBarWidth && ( < GreenBar width = { progressBarWidth } />)} < /ProgressBarPlaceholder>)} { description && ( < Description sidePadding = { contentPaddingSide } > { description } < /Description>)} < Content sidePadding = { contentPaddingSide } centerContent = { centerContent } > { children } < /Content> { bottomBar && < BottomBar sidePadding = { contentPaddingSide } > { bottomBar } < /BottomBar>} < /ModalWindow>); if ( noBackground ) { return modalWindow ; } return ( < ModalOverlay guidePanelSize = { guideOpen ? variables . LAYOUT_SIZE . GUIDE_PANEL_WIDTH : '0px' } data - test = "@modal" onClick = { ( ) => { if ( cancelable && onCancel ) { onCancel ( ) ; } } } > { header && < Header > { header } < /Header>} { modalWindow } < /ModalOverlay>); } ; export { Modal , Props as ModalProps } ;