diff options
Diffstat (limited to 'server/sonar-web/design-system/src/components/modal/Modal.tsx')
-rw-r--r-- | server/sonar-web/design-system/src/components/modal/Modal.tsx | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/server/sonar-web/design-system/src/components/modal/Modal.tsx b/server/sonar-web/design-system/src/components/modal/Modal.tsx new file mode 100644 index 00000000000..95d6213cbb8 --- /dev/null +++ b/server/sonar-web/design-system/src/components/modal/Modal.tsx @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { Global, css, useTheme } from '@emotion/react'; +import classNames from 'classnames'; +import { ReactNode } from 'react'; +import ReactModal from 'react-modal'; +import tw from 'twin.macro'; +import { themeColor } from '../../helpers'; +import { REACT_DOM_CONTAINER } from '../../helpers/constants'; +import { translate } from '../../helpers/l10n'; +import { Theme } from '../../types/theme'; +import { ButtonSecondary } from '../buttons'; +import { ModalBody } from './ModalBody'; +import { ModalFooter } from './ModalFooter'; +import { ModalHeader } from './ModalHeader'; + +ReactModal.setAppElement(REACT_DOM_CONTAINER); + +interface CommonProps { + closeOnOverlayClick?: boolean; + isLarge?: boolean; + isOpen?: boolean; + isScrollable?: boolean; + onClose: VoidFunction; +} + +interface ChildrenProp { + children: React.ReactNode; +} + +interface NotChildrenProp { + children?: never; +} + +interface SectionsProps { + body: React.ReactNode; + headerDescription?: string | ReactNode; + headerTitle: string | ReactNode; + loading?: boolean; + primaryButton?: ReactNode; + secondaryButtonLabel: ReactNode; +} + +type NotSectionsProps = { + [prop in keyof SectionsProps]?: never; +}; + +export type PropsWithChildren = CommonProps & ChildrenProp & NotSectionsProps; + +export type PropsWithSections = CommonProps & SectionsProps & NotChildrenProp; + +type Props = PropsWithChildren | PropsWithSections; + +function hasNoChildren(props: Partial<Props>): props is PropsWithSections { + return (props as PropsWithChildren).children === undefined; +} + +export function Modal({ + closeOnOverlayClick = true, + isLarge, + isOpen = true, + isScrollable = true, + onClose, + ...props +}: Props) { + const theme = useTheme(); + + return ( + <> + <Global styles={globalStyles({ theme })} /> + + <ReactModal + className={classNames('design-system-modal-contents', { large: isLarge })} + isOpen={isOpen} + onRequestClose={onClose} + overlayClassName="design-system-modal-overlay" + shouldCloseOnEsc={true} + shouldCloseOnOverlayClick={closeOnOverlayClick} + shouldFocusAfterRender={true} + shouldReturnFocusAfterClose={true} + > + {hasNoChildren(props) ? ( + <> + <ModalHeader description={props.headerDescription} title={props.headerTitle} /> + + <ModalBody isScrollable={isScrollable}>{props.body}</ModalBody> + + <ModalFooter + loading={props.loading} + primaryButton={props.primaryButton} + secondaryButton={ + <ButtonSecondary + className="js-modal-close sw-capitalize" + disabled={props.loading} + onClick={onClose} + type="reset" + > + {props.secondaryButtonLabel ?? translate('close')} + </ButtonSecondary> + } + /> + </> + ) : ( + (props as PropsWithChildren).children + )} + </ReactModal> + </> + ); +} + +const globalStyles = ({ theme }: { theme: Theme }) => css` + .design-system-modal-contents { + ${tw`sw-container sw-flex sw-flex-col`} + ${tw`sw-p-9`} + ${tw`sw-rounded-2`} + ${tw`sw-z-modal`} + + background-color: ${themeColor('modalContents')({ theme })}; + max-height: calc(100vh - 30px); + min-height: 160px; + width: 544px; + + &.large { + max-width: 1280px; + min-width: 1040px; + } + } + + .design-system-modal-overlay { + ${tw`sw-fixed sw-inset-0`} + ${tw`sw-flex sw-items-center sw-justify-center`} + ${tw`sw-z-modal-overlay`} + + background-color: ${themeColor('modalOverlay')({ theme })}; + } +`; + +Modal.Body = ModalBody; +Modal.Footer = ModalFooter; +Modal.Header = ModalHeader; |