aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/design-system/src/components/modal/Modal.tsx
diff options
context:
space:
mode:
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.tsx158
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;