diff --git a/src/common/components/modals/GoodModal.tsx b/src/common/components/modals/GoodModal.tsx index 01d630035..44a7521b2 100644 --- a/src/common/components/modals/GoodModal.tsx +++ b/src/common/components/modals/GoodModal.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; import type { SxProps } from '@mui/joy/styles/types'; -import { Box, Button, ColorPaletteProp, Divider, Modal, ModalClose, ModalDialog, ModalOverflow, Typography } from '@mui/joy'; +import { Box, Button, ColorPaletteProp, Divider, IconButton, Modal, ModalClose, ModalDialog, ModalOverflow, Typography } from '@mui/joy'; +import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen'; +import OpenInFullIcon from '@mui/icons-material/OpenInFull'; export const darkerBackdropSlotProps = { @@ -45,7 +47,7 @@ export function GoodModal(props: { /** * Show as fullscreen, ideal for mobile with large contents. */ - fullscreen?: boolean, + fullscreen?: boolean | 'button', // 'button' adds a button to toggle maximized (when uncontrolled) open: boolean, onClose?: ((event: React.BaseSyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown' | 'closeClick') => void) | undefined, disableBackdropClose?: boolean, // if true, the backdrop will not close the modal on click @@ -58,16 +60,26 @@ export function GoodModal(props: { children: React.ReactNode, }) { + // state + const [isFullscreen, setIsFullscreen] = React.useState(props.fullscreen === true); + const { onClose } = props; const showBottomClose = !!onClose && props.hideBottomClose !== true; + // fullscreen logic + const hasFullscreenButton = props.fullscreen === 'button'; + const showFullscreen = hasFullscreenButton ? isFullscreen : props.fullscreen === true; + + const toggleFullscreen = React.useCallback(() => setIsFullscreen(prev => !prev), []); + + const dialogSx: SxProps = React.useMemo(() => ({ borderRadius: 'xl', - ...(!props.fullscreen ? { + ...(!showFullscreen ? { // Centered styling boxShadow: props.themedColor ? 'none' : undefined, minWidth: { xs: 360, sm: 500, md: 600, lg: 700 }, - maxWidth: { xs: '95vw', sm: '90vw', md: 700 }, // maxWidth note: "display: flex" fills to maxWidth rather than maxWidth which created overflow in smaller screens + maxWidth: { xs: '95vw', sm: '90vw', md: 700 }, // maxWidth note: 'display: flex' fills to maxWidth rather than maxWidth which created overflow in smaller screens } : { // Fullscreen styling: no changes over the layout='fullscreen' defaults boxShadow: 'none', // removes the shadow in fullscreen @@ -81,7 +93,11 @@ export function GoodModal(props: { overflow: 'auto', } : {}), ...props.sx, - }), [props.autoOverflow, props.fullscreen, props.sx, props.themedColor]); + // reset any maxWidth set above when in fullscreen + ...showFullscreen && { + maxWidth: undefined, + }, + }), [props.autoOverflow, showFullscreen, props.sx, props.themedColor]); const modalProps = React.useMemo(() => { return props.themedColor ? { @@ -123,7 +139,7 @@ export function GoodModal(props: { {!props.noTitleBar && - + + {/* title string or component (wrapped in h1) */} + {props.title || ''} - {!!props.onClose && } + + {/* buttons */} + {(hasFullscreenButton || !!props.onClose) && ( + + {/* optional fullscreen button */} + {hasFullscreenButton && ( + + {showFullscreen ? : } + + )} + {/* optional close button */} + {!!props.onClose && } + + )} + } {props.dividers === true && }