import React from 'react'; import { isMobile } from 'react-device-detect'; import styled, { css } from 'styled-components'; import { Alignment, Layout, ThemeMode, Type, Size, FilledVariant } from '../../types'; import { IconButton } from '../Button'; import ButtonGroup, { ButtonGroupProps } from '../ButtonGroup'; import CircularProgress, { AbsolutelyCentered } from '../CircularProgress'; import Icons, { Icon, IconProps } from '../Icons'; import { InputComponent, TextAreaComponent } from '../InputField'; import Surface, { SurfaceProps, CONFIRM_MODAL_CLASSNAME } from '../Surface'; import Typography, { TypographySize, TypographyWeight } from '../Typography'; import './Dialog.scss'; const MOBILE_CONFIRM_MODAL_WIDTH = 287; const MOBILE_CONFIRM_MODAL_MAX_WIDTH = 340; const TitleDescription = styled.div` display: flex; flex-direction: column; align-items: flex-start; padding: 0px; gap: 4px; width: 100%; box-sizing: border-box; `; const Wrapper = styled.span<{ stacked: boolean }>` position: absolute; ${(props) => props.stacked && !isMobile && css` & * { align-items: flex-start; text-align: left; } `} ${(props) => props.stacked && isMobile && css` & * { text-align: center; align-items: center; } `} `; const ButtonContainer = styled.div` margin-top: 12px; width: 100%; `; const CloseButtonAbsolutePos = styled.div` position: absolute; right: 8px; top: 8px; `; export enum DialogTypes { Default = 'default', Error = 'error', Promotional = 'promotional', Input = 'input', Confirm = 'confirm', Settings = 'settings', Search = 'search', Fullscreen = 'fullscreen', Landscape = 'landscape' } export interface DialogProps { children: ButtonGroupProps['children'] | React.ReactNode; open: boolean; onClose: () => void; // icon or svg icon?: IconProps['icon'] | JSX.Element; title?: string; description?: string; type?: DialogTypes; dataTest?: string; closeBtnDataTest?: string; inputField?: Array | InputComponent | TextAreaComponent; loading?: boolean; level?: 'l0' | 'l1' | 'l2' | 'l3'; // if dialog contains non button content otherwise pass in buttom group item and auto-layout customContent?: boolean; isMobile?: boolean; disableOffClick?: boolean; padding?: boolean; forceTheme?: ThemeMode; classesToIgnore?: string[]; hideCloseButton?: boolean; zIndex?: number; disableTextSelect?: boolean; // Custom height of surface wrapper (e.g. for custom vertical alignment) customWrapperHeight?: string | number; } type DialogStyleProps = { size: SurfaceProps['size']; showCloseButton: boolean; layout: Layout; fullWidth?: boolean; mobileLayout: Layout; className?: string; }; const getTypeProps = (type: DialogTypes): DialogStyleProps => { switch (type) { case DialogTypes.Default: return { size: 'large', showCloseButton: true, layout: Layout.INLINE, mobileLayout: Layout.STACKED }; case DialogTypes.Error: return { size: 'normal', showCloseButton: false, layout: Layout.STACKED, mobileLayout: Layout.STACKED }; case DialogTypes.Promotional: return { size: 'normal', showCloseButton: true, layout: Layout.STACKED, mobileLayout: Layout.STACKED, fullWidth: true }; case DialogTypes.Input: return { size: 'xsmall', showCloseButton: false, layout: Layout.INLINE, mobileLayout: Layout.STACKED }; case DialogTypes.Confirm: return { size: 'small', showCloseButton: false, layout: Layout.INLINE, fullWidth: true, mobileLayout: Layout.INLINE, className: CONFIRM_MODAL_CLASSNAME }; case DialogTypes.Search: return { size: 'full-width', showCloseButton: false, layout: Layout.INLINE, mobileLayout: Layout.STACKED }; case DialogTypes.Settings: return { size: 'xxlarge', showCloseButton: false, layout: Layout.INLINE, mobileLayout: Layout.STACKED }; case DialogTypes.Landscape: return { size: 'xlarge', showCloseButton: true, layout: Layout.INLINE, mobileLayout: Layout.STACKED }; case DialogTypes.Fullscreen: return { size: 'full-screen', showCloseButton: true, layout: Layout.INLINE, mobileLayout: Layout.STACKED }; default: return { size: 'large', showCloseButton: true, layout: Layout.INLINE, mobileLayout: Layout.STACKED }; } }; const getSurfaceStyle = (type: DialogTypes, isMobile?: boolean, hasDescription?: boolean): React.CSSProperties => { switch (type) { // Stacked layout items need to be left aligned case DialogTypes.Error: case DialogTypes.Promotional: return { alignItems: 'flex-start', textAlign: 'left' }; // All non-stacked cased use default alignment case DialogTypes.Confirm: const gap = isMobile ? 8 : 16; if (hasDescription) { return { display: 'flex', gap: `${gap}px`, padding: `${isMobile ? 16 : 20}px`, minWidth: isMobile ? `${MOBILE_CONFIRM_MODAL_WIDTH}px` : '', maxWidth: isMobile ? `${MOBILE_CONFIRM_MODAL_MAX_WIDTH}px` : '' }; } return { display: 'flex', gap: `${gap * 2}px`, padding: `${isMobile ? 16 : 20}px`, width: isMobile ? `${MOBILE_CONFIRM_MODAL_WIDTH}px` : '' }; default: return {}; } }; // TODO: Document export default function Dialog({ icon, title, description, inputField, type = DialogTypes.Default, children, open, dataTest, loading, level = 'l3', customContent, onClose, isMobile, disableOffClick, padding = true, closeBtnDataTest, forceTheme, classesToIgnore, hideCloseButton = false, zIndex, disableTextSelect = false, customWrapperHeight }: DialogProps) { // TODO: default becomes stacked if mobile const { className, size, showCloseButton: typeShowsCloseButton, layout, fullWidth, mobileLayout } = getTypeProps(type); const showCloseButton = typeShowsCloseButton && !hideCloseButton; const closeButton = ( ); const spinner = ( ); // Both variables only defined the type of the dialog is Confirm. // Otherwise, will use Typography's default values. const titleSize = isMobile && type === DialogTypes.Confirm ? TypographySize.LARGE : TypographySize.H4; const align = isMobile && type === DialogTypes.Confirm ? Alignment.CENTER : undefined; return ( {}} // No-op function ensures nothing happens on close showClose={showCloseButton} style={{ ...getSurfaceStyle(type, isMobile, !!description), userSelect: disableTextSelect ? 'none' : 'auto' }} padding={padding} forceTheme={forceTheme} classesToIgnore={classesToIgnore} zIndex={zIndex} > {icon && ( <> {/* Icon */} {icon && typeof icon === 'string' && ( )} {/* SVG */} {icon && typeof icon === 'object' && icon} )} {showCloseButton && closeButton} {title && ( {title && ( {title} )} {description && ( {description} )} )} {loading ? spinner : inputField} {!customContent && ( {children as ButtonGroupProps['children']} )} {customContent && children} ); }