]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19170 Create new Modal component in design-system/
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>
Fri, 12 May 2023 14:25:44 +0000 (16:25 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 17 May 2023 20:02:41 +0000 (20:02 +0000)
17 files changed:
server/sonar-web/design-system/package.json
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/components/modal/Modal.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/ModalBody.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/ModalFooter.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/ModalHeader.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/__tests__/Modal-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/__tests__/ModalBody-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/__tests__/ModalFooter-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/__tests__/ModalHeader-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalBody-test.tsx.snap [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalFooter-test.tsx.snap [new file with mode: 0644]
server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalHeader-test.tsx.snap [new file with mode: 0644]
server/sonar-web/design-system/src/helpers/constants.ts
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/tailwind.base.config.js
server/sonar-web/yarn.lock

index bedf128e5e12a0a6e2b2b48a2531f7c9f8601e8b..45633e20409a53b854e172404bf7bec17f8fec42 100644 (file)
@@ -62,6 +62,7 @@
     "react-helmet-async": "1.3.0",
     "react-highlight-words": "0.20.0",
     "react-intl": "6.2.5",
+    "react-modal": "3.16.1",
     "react-router-dom": "6.10.0",
     "react-select": "5.7.2",
     "tailwindcss": "3.3.1"
index 74092bf20ace271f0f0494f4fab5b7f896029cd6..7fefefbfc7f2eb5b72e20cc48018646c4cf70fa4 100644 (file)
@@ -71,5 +71,6 @@ export * from './buttons';
 export { ClipboardIconButton } from './clipboard';
 export * from './icons';
 export * from './layouts';
+export * from './modal/Modal';
 export * from './popups';
 export * from './subnavigation';
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 (file)
index 0000000..95d6213
--- /dev/null
@@ -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;
diff --git a/server/sonar-web/design-system/src/components/modal/ModalBody.tsx b/server/sonar-web/design-system/src/components/modal/ModalBody.tsx
new file mode 100644 (file)
index 0000000..5a3b1f1
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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 styled from '@emotion/styled';
+import classNames from 'classnames';
+import { ReactNode } from 'react';
+import tw from 'twin.macro';
+import { themeColor } from '../../helpers/theme';
+
+interface Props {
+  children: ReactNode;
+  isScrollable?: boolean;
+}
+
+export function ModalBody({ children, isScrollable = true }: Props) {
+  return <StyledMain className={classNames({ scrollable: isScrollable })}>{children}</StyledMain>;
+}
+
+const StyledMain = styled.main`
+  ${tw`sw-body-sm`}
+  ${tw`sw-pr-3`} // to accomodate a possible scrollbar
+  ${tw`sw-my-12`}
+  ${tw`sw-overflow-x-hidden`}
+
+  color: ${themeColor('pageContent')};
+
+  &.scrollable {
+    overflow-y: auto;
+  }
+`;
diff --git a/server/sonar-web/design-system/src/components/modal/ModalFooter.tsx b/server/sonar-web/design-system/src/components/modal/ModalFooter.tsx
new file mode 100644 (file)
index 0000000..72d75b6
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 styled from '@emotion/styled';
+import tw from 'twin.macro';
+import { DeferredSpinner } from '../DeferredSpinner';
+
+interface Props {
+  loading?: boolean;
+  primaryButton?: React.ReactNode;
+  secondaryButton: React.ReactNode;
+}
+
+export function ModalFooter({ loading = false, primaryButton, secondaryButton }: Props) {
+  return (
+    <StyledFooter>
+      <DeferredSpinner loading={loading} />
+      {primaryButton}
+      {secondaryButton}
+    </StyledFooter>
+  );
+}
+
+const StyledFooter = styled.footer`
+  ${tw`sw-flex sw-gap-3 sw-items-center sw-justify-end`}
+`;
diff --git a/server/sonar-web/design-system/src/components/modal/ModalHeader.tsx b/server/sonar-web/design-system/src/components/modal/ModalHeader.tsx
new file mode 100644 (file)
index 0000000..eaa02e1
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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 styled from '@emotion/styled';
+import { ReactNode } from 'react';
+import tw from 'twin.macro';
+import { themeColor } from '../../helpers/theme';
+
+interface Props {
+  description?: string | ReactNode;
+  title: string | ReactNode;
+}
+
+export function ModalHeader({ description, title }: Props) {
+  return (
+    <header>
+      <Title>{title}</Title>
+      {description && <Description>{description}</Description>}
+    </header>
+  );
+}
+
+const Description = styled.p`
+  ${tw`sw-body-sm`}
+  ${tw`sw-mt-2`}
+
+  color: ${themeColor('pageContent')};
+`;
+
+const Title = styled.p`
+  ${tw`sw-heading-lg`}
+
+  color: ${themeColor('pageTitle')};
+`;
diff --git a/server/sonar-web/design-system/src/components/modal/__tests__/Modal-test.tsx b/server/sonar-web/design-system/src/components/modal/__tests__/Modal-test.tsx
new file mode 100644 (file)
index 0000000..86085f5
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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 { screen } from '@testing-library/react';
+import { render } from '../../../helpers/testUtils';
+import { Modal, PropsWithChildren, PropsWithSections } from '../Modal';
+
+it('should render default modal with predefined content', async () => {
+  setupPredefinedContent({
+    body: 'Modal body',
+    headerTitle: 'Hello',
+    headerDescription: 'Does this look OK?',
+    secondaryButtonLabel: undefined, // should use the default of 'Close'
+  });
+
+  expect(await screen.findByText('Modal body')).toBeVisible();
+  expect(await screen.findByText('Hello')).toBeVisible();
+  expect(await screen.findByText('Does this look OK?')).toBeVisible();
+  expect(await screen.findByRole('button', { name: 'close' })).toBeVisible();
+});
+
+it('should request close when pressing esc', async () => {
+  const onClose = jest.fn();
+  const { user } = setupPredefinedContent({ onClose });
+
+  await user.keyboard('{Escape}');
+
+  expect(onClose).toHaveBeenCalled();
+});
+
+it('should render modal with loose content', async () => {
+  setupLooseContent(undefined, <div>Hello</div>);
+
+  expect(await screen.findByText('Hello')).toBeVisible();
+});
+
+it('should request close when pressing esc on loose content', async () => {
+  const onClose = jest.fn();
+  const { user } = setupLooseContentWithMultipleChildren({ onClose });
+
+  await user.keyboard('{Escape}');
+
+  expect(onClose).toHaveBeenCalled();
+});
+
+function setupPredefinedContent(props: Partial<PropsWithSections> = {}) {
+  return render(
+    <Modal
+      body="Body"
+      headerTitle="Hello"
+      onClose={jest.fn()}
+      secondaryButtonLabel="Close"
+      {...props}
+    />
+  );
+}
+
+function setupLooseContent(props: Partial<PropsWithChildren> = {}, children = <div />) {
+  return render(
+    <Modal onClose={jest.fn()} {...props}>
+      {children}
+    </Modal>
+  );
+}
+
+function setupLooseContentWithMultipleChildren(props: Partial<PropsWithChildren> = {}) {
+  return render(
+    <Modal onClose={jest.fn()} {...props}>
+      <div>Hello there!</div>
+      <div>How are you?</div>
+    </Modal>
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/modal/__tests__/ModalBody-test.tsx b/server/sonar-web/design-system/src/components/modal/__tests__/ModalBody-test.tsx
new file mode 100644 (file)
index 0000000..690cd99
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 { render } from '../../../helpers/testUtils';
+import { ModalBody } from '../ModalBody';
+
+it('renders with children', () => {
+  const children = <div>Hello!</div>;
+  const { container } = setup(children);
+
+  expect(container).toMatchSnapshot();
+});
+
+function setup(children = <div />) {
+  return render(<ModalBody>{children}</ModalBody>);
+}
diff --git a/server/sonar-web/design-system/src/components/modal/__tests__/ModalFooter-test.tsx b/server/sonar-web/design-system/src/components/modal/__tests__/ModalFooter-test.tsx
new file mode 100644 (file)
index 0000000..ab2c19c
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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 { render } from '../../../helpers/testUtils';
+import { FCProps } from '../../../types/misc';
+import { ModalFooter } from '../ModalFooter';
+
+it('should render with secondary button', () => {
+  const { container } = setupWithProps({
+    secondaryButton: (
+      <button onClick={() => {}} type="button">
+        Close
+      </button>
+    ),
+  });
+
+  expect(container).toMatchSnapshot();
+});
+
+it('should render with primary and secondary buttons', () => {
+  const { container } = setupWithProps({
+    primaryButton: (
+      <button onClick={undefined} type="button">
+        Primary
+      </button>
+    ),
+    secondaryButton: (
+      <button onClick={undefined} type="reset">
+        Reset
+      </button>
+    ),
+  });
+
+  expect(container).toMatchSnapshot();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof ModalFooter>> = {}) {
+  return render(<ModalFooter secondaryButton={<div />} {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/modal/__tests__/ModalHeader-test.tsx b/server/sonar-web/design-system/src/components/modal/__tests__/ModalHeader-test.tsx
new file mode 100644 (file)
index 0000000..a3a163a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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 { render } from '../../../helpers/testUtils';
+import { FCProps } from '../../../types/misc';
+import { ModalHeader } from '../ModalHeader';
+
+it('should use the default title if not provided', () => {
+  const { container } = setupWithProps();
+
+  expect(container).toMatchSnapshot();
+});
+
+it('should render with title', () => {
+  const { container } = setupWithProps({ title: 'Foo' });
+
+  expect(container).toMatchSnapshot();
+});
+
+it('should render with title and description', () => {
+  const { container } = setupWithProps({ title: 'Foo', description: 'Bar' });
+
+  expect(container).toMatchSnapshot();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof ModalHeader>> = {}) {
+  return render(<ModalHeader title="Modal title" {...props} />);
+}
diff --git a/server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalBody-test.tsx.snap b/server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalBody-test.tsx.snap
new file mode 100644 (file)
index 0000000..8f64b28
--- /dev/null
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders with children 1`] = `
+.emotion-0 {
+  font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+  font-size: 0.875rem;
+  line-height: 1.25rem;
+  font-weight: 400;
+  padding-right: 0.75rem;
+  margin-top: 3rem;
+  margin-bottom: 3rem;
+  overflow-x: hidden;
+  color: rgb(62,67,87);
+}
+
+.emotion-0.scrollable {
+  overflow-y: auto;
+}
+
+<div>
+  <main
+    class="scrollable emotion-0 emotion-1"
+  >
+    <div>
+      Hello!
+    </div>
+  </main>
+</div>
+`;
diff --git a/server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalFooter-test.tsx.snap b/server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalFooter-test.tsx.snap
new file mode 100644 (file)
index 0000000..250bd88
--- /dev/null
@@ -0,0 +1,66 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render with primary and secondary buttons 1`] = `
+.emotion-0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-align-items: center;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  -webkit-box-pack: end;
+  -ms-flex-pack: end;
+  -webkit-justify-content: flex-end;
+  justify-content: flex-end;
+  gap: 0.75rem;
+}
+
+<div>
+  <footer
+    class="emotion-0 emotion-1"
+  >
+    <button
+      type="button"
+    >
+      Primary
+    </button>
+    <button
+      type="reset"
+    >
+      Reset
+    </button>
+  </footer>
+</div>
+`;
+
+exports[`should render with secondary button 1`] = `
+.emotion-0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-align-items: center;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  -webkit-box-pack: end;
+  -ms-flex-pack: end;
+  -webkit-justify-content: flex-end;
+  justify-content: flex-end;
+  gap: 0.75rem;
+}
+
+<div>
+  <footer
+    class="emotion-0 emotion-1"
+  >
+    <button
+      type="button"
+    >
+      Close
+    </button>
+  </footer>
+</div>
+`;
diff --git a/server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalHeader-test.tsx.snap b/server/sonar-web/design-system/src/components/modal/__tests__/__snapshots__/ModalHeader-test.tsx.snap
new file mode 100644 (file)
index 0000000..10d1204
--- /dev/null
@@ -0,0 +1,75 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render with title 1`] = `
+.emotion-0 {
+  font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+  font-size: 1.5rem;
+  line-height: 1.75rem;
+  font-weight: 600;
+  color: rgb(29,33,47);
+}
+
+<div>
+  <header>
+    <p
+      class="emotion-0 emotion-1"
+    >
+      Foo
+    </p>
+  </header>
+</div>
+`;
+
+exports[`should render with title and description 1`] = `
+.emotion-0 {
+  font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+  font-size: 1.5rem;
+  line-height: 1.75rem;
+  font-weight: 600;
+  color: rgb(29,33,47);
+}
+
+.emotion-2 {
+  font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+  font-size: 0.875rem;
+  line-height: 1.25rem;
+  font-weight: 400;
+  margin-top: 0.5rem;
+  color: rgb(62,67,87);
+}
+
+<div>
+  <header>
+    <p
+      class="emotion-0 emotion-1"
+    >
+      Foo
+    </p>
+    <p
+      class="emotion-2 emotion-3"
+    >
+      Bar
+    </p>
+  </header>
+</div>
+`;
+
+exports[`should use the default title if not provided 1`] = `
+.emotion-0 {
+  font-family: Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
+  font-size: 1.5rem;
+  line-height: 1.75rem;
+  font-weight: 600;
+  color: rgb(29,33,47);
+}
+
+<div>
+  <header>
+    <p
+      class="emotion-0 emotion-1"
+    >
+      Modal title
+    </p>
+  </header>
+</div>
+`;
index 9d87c9245ee9a53ab5d731dccf030f5b98a2d6f3..e7f44f2f2aa1d9054ba3acb7feac9507ce31c51f 100644 (file)
@@ -22,7 +22,7 @@ import { theme } from 'twin.macro';
 
 export const DEFAULT_LOCALE = 'en';
 export const IS_SSR = typeof window === 'undefined';
-export const REACT_DOM_CONTAINER = '#___gatsby';
+export const REACT_DOM_CONTAINER = '#content';
 
 export const RULE_STATUSES = ['READY', 'BETA', 'DEPRECATED'];
 
@@ -72,3 +72,5 @@ export const CORE_CONCEPTS_WIDTH = 350;
 export const DARK_THEME_ID = 'dark-theme';
 
 export const OPACITY_20_PERCENT = 0.2;
+
+export const OPACITY_75_PERCENT = 0.75;
index cbcebc1e18504a00e121d43d780a2897c543f97f..7a3756efbdec70b2cc988e7ed04f88d0f381b112 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { OPACITY_20_PERCENT } from '../helpers/constants';
+import { OPACITY_20_PERCENT, OPACITY_75_PERCENT } from '../helpers/constants';
 import COLORS from './colors';
 
 const primary = {
@@ -92,6 +92,10 @@ export const lightTheme = {
     popup: COLORS.white,
     popupBorder: secondary.default,
 
+    // modal
+    modalContents: COLORS.white,
+    modalOverlay: [...COLORS.blueGrey[900], OPACITY_75_PERCENT],
+
     // dropdown menu
     dropdownMenu: COLORS.white,
     dropdownMenuHover: secondary.light,
index 3971e725b8bfc803ceb3051f892f7404c122afe9..af6c8b43a7f8e39673c8aa746f829c9692e172af 100644 (file)
@@ -110,6 +110,8 @@ module.exports = {
       'global-popup': '5000',
       'dropdown-menu': '7500',
       tooltip: '8000',
+      'modal-overlay': 8500,
+      modal: '9000',
     },
     extend: {
       width: {
index 96fdeac3e6166c33d0011a49084d4df92e468694..1847ec4d86aaee2e2f4f0d1d32382f2e6b996436 100644 (file)
@@ -6173,6 +6173,7 @@ __metadata:
     react-helmet-async: 1.3.0
     react-highlight-words: 0.20.0
     react-intl: 6.2.5
+    react-modal: 3.16.1
     react-router-dom: 6.10.0
     react-select: 5.7.2
     tailwindcss: 3.3.1