diff --git a/app/src/atoms/Modal/ModalHeader.tsx b/app/src/atoms/Modal/ModalHeader.tsx new file mode 100644 index 00000000000..5eb6a701341 --- /dev/null +++ b/app/src/atoms/Modal/ModalHeader.tsx @@ -0,0 +1,77 @@ +import * as React from 'react' +import { css } from 'styled-components' +import { + Btn, + Icon, + TYPOGRAPHY, + Flex, + ALIGN_CENTER, + JUSTIFY_SPACE_BETWEEN, + SPACING, +} from '@opentrons/components' + +import { StyledText } from '../text' +import { Divider } from '../structure' +import type { IconProps } from '@opentrons/components' + +export interface ModalHeaderProps { + onClose?: React.MouseEventHandler + title: React.ReactNode + icon?: IconProps + closeButton?: JSX.Element +} + +const closeIconStyles = css` + display: flex; + justify-content: center; + align-items: center; + border-radius: 14px; + width: ${SPACING.spacingL}; + height: ${SPACING.spacingL}; + &:hover { + background-color: #16212d26; + } + + &:active { + background-color: #16212d40; + } +` + +export const ModalHeader = (props: ModalHeaderProps): JSX.Element => { + const { icon, onClose, title, closeButton } = props + return ( + <> + + + {icon != null && } + + {title} + + + {closeButton != null + ? { closeButton } + : onClose != null && ( + + + + )} + + + + ) +} diff --git a/app/src/atoms/Modal/ModalShell.tsx b/app/src/atoms/Modal/ModalShell.tsx new file mode 100644 index 00000000000..072ea5a66b7 --- /dev/null +++ b/app/src/atoms/Modal/ModalShell.tsx @@ -0,0 +1,116 @@ +import * as React from 'react' + +import { + Box, + Flex, + StyleProps, + COLORS, + SPACING, + POSITION_FIXED, + POSITION_ABSOLUTE, + ALIGN_CENTER, + JUSTIFY_CENTER, + POSITION_RELATIVE, + OVERFLOW_AUTO, + POSITION_STICKY, + BORDERS, +} from '@opentrons/components' + +const BASE_STYLE = { + position: POSITION_ABSOLUTE, + alignItems: ALIGN_CENTER, + justifyContent: JUSTIFY_CENTER, + top: 0, + right: 0, + bottom: 0, + left: 0, + width: '100%', + height: '100%', +} as const + +const MODAL_STYLE = { + backgroundColor: COLORS.white, + position: POSITION_RELATIVE, + overflowY: OVERFLOW_AUTO, + maxHeight: '100%', + width: '100%', + margin: SPACING.spacing5, + borderRadius: BORDERS.radiusSoftCorners, + boxShadow: BORDERS.smallDropShadow, +} as const + +const HEADER_STYLE = { + backgroundColor: COLORS.white, + position: POSITION_STICKY, + top: 0, +} as const + +const FOOTER_STYLE = { + backgroundColor: COLORS.white, + position: POSITION_STICKY, + bottom: 0, + boxShadow: '0px 3px 6px 0px #0000003B', +} as const + +export interface ModalShellProps extends StyleProps { + /** Optional close on outside click **/ + onOutsideClick?: React.MouseEventHandler + /** Optional sticky header */ + header?: React.ReactNode + /** Optional sticky footer */ + footer?: React.ReactNode + /** Modal content */ + children: React.ReactNode +} + +/** + * A ModalShell is a layout component for building more specific modals. + * + * It includes: + * - An overlay + * - A content area, with `overflow-y: auto` and customizable with style props + * - An optional sticky header + * - An optional sticky footer + * - An optional onOutsideClick function + */ +export function ModalShell(props: ModalShellProps): JSX.Element { + const { + onOutsideClick, + zIndex = 10, + header, + footer, + children, + ...styleProps + } = props + + return ( + { + e.stopPropagation() + if (onOutsideClick != null) onOutsideClick(e) + }} + > + + { + e.stopPropagation() + }} + > + {header != null ? {header} : null} + {children} + {footer != null ? {footer} : null} + + + + ) +} diff --git a/app/src/atoms/Modal/index.tsx b/app/src/atoms/Modal/index.tsx index f45aea92816..87efd3fe127 100644 --- a/app/src/atoms/Modal/index.tsx +++ b/app/src/atoms/Modal/index.tsx @@ -3,6 +3,7 @@ import { css } from 'styled-components' import { Btn, Icon, + Box, BaseModal, BaseModalProps, TYPOGRAPHY, @@ -14,17 +15,23 @@ import { BORDERS, } from '@opentrons/components' +import { useFeatureFlag } from '../../redux/config' import { StyledText } from '../text' import { Divider } from '../structure' - +import { ModalShell } from './ModalShell' +import { ModalHeader } from './ModalHeader' import type { IconProps } from '@opentrons/components' type ModalType = 'info' | 'warning' | 'error' +export * from './ModalShell' +export * from './ModalHeader' + export interface ModalProps extends BaseModalProps { type?: ModalType onClose?: React.MouseEventHandler closeOnOutsideClick?: boolean title?: React.ReactNode + footer?: React.ReactNode children?: React.ReactNode icon?: IconProps } @@ -54,7 +61,8 @@ export const Modal = (props: ModalProps): JSX.Element => { children, maxHeight, } = props - const header = + const liquidSetupEnabled = useFeatureFlag('enableLiquidSetup') + const defaultHeader = title != null ? ( <> { ) : null + const modalHeader = ( + + ) - return ( + return liquidSetupEnabled ? ( + + + {children} + + + ) : ( } > - + - + ) }