Browse Source

Merge pull request #18127 from overleaf/rd-bs-modal

[web] - Migrate the Modal to Bootstrap 5 on the Account Settings page

GitOrigin-RevId: 90799125f837742b4887eab762ca9ff84a67e70b
Rebeka Dekany 2 years ago
parent
commit
ea826658a0

+ 15 - 10
services/web/frontend/js/features/settings/components/emails/actions/make-primary/confirmation-modal.tsx

@@ -1,8 +1,13 @@
 import { useTranslation, Trans } from 'react-i18next'
-import { Modal } from 'react-bootstrap'
 import AccessibleModal from '../../../../../../shared/components/accessible-modal'
 import { MergeAndOverride } from '../../../../../../../../types/utils'
 import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
+import OLModal, {
+  OLModalBody,
+  OLModalFooter,
+  OLModalHeader,
+  OLModalTitle,
+} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
 
 type ConfirmationModalProps = MergeAndOverride<
   React.ComponentProps<typeof AccessibleModal>,
@@ -24,11 +29,11 @@ function ConfirmationModal({
   const { t } = useTranslation()
 
   return (
-    <AccessibleModal show={show} onHide={onHide}>
-      <Modal.Header closeButton>
-        <Modal.Title>{t('confirm_primary_email_change')}</Modal.Title>
-      </Modal.Header>
-      <Modal.Body className="modal-body-share">
+    <OLModal show={show} onHide={onHide}>
+      <OLModalHeader closeButton>
+        <OLModalTitle>{t('confirm_primary_email_change')}</OLModalTitle>
+      </OLModalHeader>
+      <OLModalBody>
         <p>
           <Trans
             i18nKey="do_you_want_to_change_your_primary_email_address_to"
@@ -39,8 +44,8 @@ function ConfirmationModal({
           />
         </p>
         <p className="mb-0">{t('log_in_with_primary_email_address')}</p>
-      </Modal.Body>
-      <Modal.Footer>
+      </OLModalBody>
+      <OLModalFooter>
         <ButtonWrapper
           variant="secondary"
           onClick={onHide}
@@ -59,8 +64,8 @@ function ConfirmationModal({
         >
           {t('confirm')}
         </ButtonWrapper>
-      </Modal.Footer>
-    </AccessibleModal>
+      </OLModalFooter>
+    </OLModal>
   )
 }
 

+ 13 - 8
services/web/frontend/js/features/settings/components/leave/modal-content.tsx

@@ -1,10 +1,15 @@
 import { useState, Dispatch, SetStateAction } from 'react'
-import { Modal } from 'react-bootstrap'
 import { useTranslation, Trans } from 'react-i18next'
 import getMeta from '../../../../utils/meta'
 import LeaveModalForm, { LeaveModalFormProps } from './modal-form'
 import { ExposedSettings } from '../../../../../../types/exposed-settings'
 import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
+import {
+  OLModalBody,
+  OLModalFooter,
+  OLModalHeader,
+  OLModalTitle,
+} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
 
 type LeaveModalContentProps = {
   handleHide: () => void
@@ -50,11 +55,11 @@ function LeaveModalContent({
 
   return (
     <>
-      <Modal.Header closeButton>
-        <Modal.Title>{t('delete_account')}</Modal.Title>
-      </Modal.Header>
+      <OLModalHeader closeButton>
+        <OLModalTitle>{t('delete_account')}</OLModalTitle>
+      </OLModalHeader>
 
-      <Modal.Body>
+      <OLModalBody>
         <p>
           <Trans
             i18nKey="delete_account_warning_message_3"
@@ -66,9 +71,9 @@ function LeaveModalContent({
           isFormValid={isFormValid}
           setIsFormValid={setIsFormValid}
         />
-      </Modal.Body>
+      </OLModalBody>
 
-      <Modal.Footer>
+      <OLModalFooter>
         <ButtonWrapper
           disabled={inFlight}
           onClick={handleHide}
@@ -86,7 +91,7 @@ function LeaveModalContent({
         >
           {inFlight ? <>{t('deleting')}…</> : t('delete')}
         </ButtonWrapper>
-      </Modal.Footer>
+      </OLModalFooter>
     </>
   )
 }

+ 4 - 4
services/web/frontend/js/features/settings/components/leave/modal.tsx

@@ -1,6 +1,6 @@
 import { useState, useCallback } from 'react'
-import AccessibleModal from '../../../../shared/components/accessible-modal'
 import LeaveModalContent from './modal-content'
+import OLModal from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
 
 type LeaveModalProps = {
   isOpen: boolean
@@ -17,19 +17,19 @@ function LeaveModal({ isOpen, handleClose }: LeaveModalProps) {
   }, [handleClose, inFlight])
 
   return (
-    <AccessibleModal
+    <OLModal
       animation
       show={isOpen}
       onHide={handleHide}
       id="leave-modal"
-      backdrop="static"
+      bs3Props={{ backdrop: 'static' }}
     >
       <LeaveModalContent
         handleHide={handleHide}
         inFlight={inFlight}
         setInFlight={setInFlight}
       />
-    </AccessibleModal>
+    </OLModal>
   )
 }
 

+ 15 - 11
services/web/frontend/js/features/settings/components/linking/integration-widget.tsx

@@ -1,12 +1,16 @@
 import { useCallback, useState, ReactNode } from 'react'
 import { useTranslation } from 'react-i18next'
-import AccessibleModal from '../../../../shared/components/accessible-modal'
-import { Modal } from 'react-bootstrap'
 import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
 import getMeta from '../../../../utils/meta'
 import { sendMB } from '../../../../infrastructure/event-tracking'
 import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
 import { bsVersion } from '@/features/utils/bootstrap-5'
+import OLModal, {
+  OLModalBody,
+  OLModalFooter,
+  OLModalHeader,
+  OLModalTitle,
+} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
 
 function trackUpgradeClick(integration: string) {
   sendMB('settings-upgrade-click', { integration })
@@ -201,16 +205,16 @@ function UnlinkConfirmationModal({
   }
 
   return (
-    <AccessibleModal show={show} onHide={handleHide}>
-      <Modal.Header closeButton>
-        <Modal.Title>{title}</Modal.Title>
-      </Modal.Header>
+    <OLModal show={show} onHide={handleHide}>
+      <OLModalHeader closeButton>
+        <OLModalTitle>{title}</OLModalTitle>
+      </OLModalHeader>
 
-      <Modal.Body className="modal-body-share">
+      <OLModalBody>
         <p>{content}</p>
-      </Modal.Body>
+      </OLModalBody>
 
-      <Modal.Footer>
+      <OLModalFooter>
         <form action={unlinkPath} method="POST" className="form-inline">
           <input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
           <ButtonWrapper
@@ -232,7 +236,7 @@ function UnlinkConfirmationModal({
             {t('unlink')}
           </ButtonWrapper>
         </form>
-      </Modal.Footer>
-    </AccessibleModal>
+      </OLModalFooter>
+    </OLModal>
   )
 }

+ 16 - 12
services/web/frontend/js/features/settings/components/linking/sso-widget.tsx

@@ -1,14 +1,18 @@
 import { useCallback, useState } from 'react'
 import { useTranslation } from 'react-i18next'
-import { Modal } from 'react-bootstrap'
 import { FetchError } from '../../../../infrastructure/fetch-json'
-import AccessibleModal from '../../../../shared/components/accessible-modal'
 import IEEELogo from '../../../../shared/svgs/ieee-logo'
 import GoogleLogo from '../../../../shared/svgs/google-logo'
 import OrcidLogo from '../../../../shared/svgs/orcid-logo'
 import LinkingStatus from './status'
 import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
 import { bsVersion } from '@/features/utils/bootstrap-5'
+import OLModal, {
+  OLModalBody,
+  OLModalFooter,
+  OLModalHeader,
+  OLModalTitle,
+} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
 
 const providerLogos: { readonly [p: string]: JSX.Element } = {
   collabratec: <IEEELogo />,
@@ -165,18 +169,18 @@ function UnlinkConfirmModal({
   const { t } = useTranslation()
 
   return (
-    <AccessibleModal show={show} onHide={handleHide}>
-      <Modal.Header closeButton>
-        <Modal.Title>
+    <OLModal show={show} onHide={handleHide}>
+      <OLModalHeader closeButton>
+        <OLModalTitle>
           {t('unlink_provider_account_title', { provider: title })}
-        </Modal.Title>
-      </Modal.Header>
+        </OLModalTitle>
+      </OLModalHeader>
 
-      <Modal.Body className="modal-body-share">
+      <OLModalBody>
         <p>{t('unlink_provider_account_warning', { provider: title })}</p>
-      </Modal.Body>
+      </OLModalBody>
 
-      <Modal.Footer>
+      <OLModalFooter>
         <ButtonWrapper
           variant="secondary"
           onClick={handleHide}
@@ -194,7 +198,7 @@ function UnlinkConfirmModal({
         >
           {t('unlink')}
         </ButtonWrapper>
-      </Modal.Footer>
-    </AccessibleModal>
+      </OLModalFooter>
+    </OLModal>
   )
 }

+ 130 - 0
services/web/frontend/js/features/ui/components/bootstrap-5/wrappers/ol-modal.tsx

@@ -0,0 +1,130 @@
+import { Modal as BS5Modal } from 'react-bootstrap-5'
+import {
+  Modal as BS3Modal,
+  ModalProps as BS3ModalProps,
+  ModalHeaderProps as BS3ModalHeaderProps,
+  ModalTitleProps as BS3ModalTitleProps,
+  ModalBodyProps as BS3ModalBodyProps,
+  ModalFooterProps as BS3ModalFooterProps,
+} from 'react-bootstrap'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import AccessibleModal from '@/shared/components/accessible-modal'
+
+type OLModalProps = React.ComponentProps<typeof BS5Modal> & {
+  bs3Props?: Record<string, unknown>
+  size?: 'sm' | 'lg'
+  onHide: () => void
+}
+
+type OLModalHeaderProps = React.ComponentProps<typeof BS5Modal> & {
+  bs3Props?: Record<string, unknown>
+}
+
+type OLModalTitleProps = React.ComponentProps<typeof BS5Modal> & {
+  bs3Props?: Record<string, unknown>
+}
+
+type OLModalBodyProps = React.ComponentProps<typeof BS5Modal> & {
+  bs3Props?: Record<string, unknown>
+}
+
+type OLModalFooterProps = React.ComponentProps<typeof BS5Modal> & {
+  bs3Props?: Record<string, unknown>
+}
+
+export default function OLModal({ children, ...props }: OLModalProps) {
+  const { bs3Props, ...bs5Props } = props
+
+  const bs3ModalProps: BS3ModalProps = {
+    bsClass: bs5Props.bsPrefix,
+    bsSize: bs5Props.size,
+    show: bs5Props.show,
+    onHide: bs5Props.onHide,
+    backdrop: bs5Props.backdrop,
+    animation: bs5Props.animation,
+    dialogComponent: bs5Props.dialogAs,
+    ...bs3Props,
+  }
+
+  return (
+    <BootstrapVersionSwitcher
+      bs3={<AccessibleModal {...bs3ModalProps}>{children}</AccessibleModal>}
+      bs5={<BS5Modal {...bs5Props}>{children}</BS5Modal>}
+    />
+  )
+}
+
+export function OLModalHeader({
+  children,
+  closeButton,
+  ...props
+}: OLModalHeaderProps) {
+  const { bs3Props, ...bs5Props } = props
+
+  const bs3ModalProps: BS3ModalHeaderProps = {
+    bsClass: bs5Props.bsPrefix,
+    onHide: bs5Props.onHide,
+    closeButton: bs5Props.closeButton,
+    closeLabel: bs5Props.closeLabel,
+  }
+  return (
+    <BootstrapVersionSwitcher
+      bs3={
+        <BS3Modal.Header {...bs3ModalProps} closeButton={closeButton}>
+          {children}
+        </BS3Modal.Header>
+      }
+      bs5={
+        <BS5Modal.Header {...bs5Props} closeButton={closeButton}>
+          {children}
+        </BS5Modal.Header>
+      }
+    />
+  )
+}
+
+export function OLModalTitle({ children, ...props }: OLModalTitleProps) {
+  const { bs3Props, ...bs5Props } = props
+
+  const bs3ModalProps: BS3ModalTitleProps = {
+    componentClass: bs5Props.as,
+  }
+  return (
+    <BootstrapVersionSwitcher
+      bs3={<BS3Modal.Title {...bs3ModalProps}>{children}</BS3Modal.Title>}
+      bs5={<BS5Modal.Title {...bs5Props}>{children}</BS5Modal.Title>}
+    />
+  )
+}
+
+export function OLModalBody({ children, ...props }: OLModalBodyProps) {
+  const { bs3Props, ...bs5Props } = props
+
+  const bs3ModalProps: BS3ModalBodyProps = {
+    componentClass: bs5Props.as,
+    bsClass: bs5Props.className,
+  }
+
+  return (
+    <BootstrapVersionSwitcher
+      bs3={<BS3Modal.Body {...bs3ModalProps}>{children}</BS3Modal.Body>}
+      bs5={<BS5Modal.Body {...bs5Props}>{children}</BS5Modal.Body>}
+    />
+  )
+}
+
+export function OLModalFooter({ children, ...props }: OLModalFooterProps) {
+  const { bs3Props, ...bs5Props } = props
+
+  const bs3ModalProps: BS3ModalFooterProps = {
+    componentClass: bs5Props.as,
+    bsClass: bs5Props.className,
+  }
+
+  return (
+    <BootstrapVersionSwitcher
+      bs3={<BS3Modal.Footer {...bs3ModalProps}>{children}</BS3Modal.Footer>}
+      bs5={<BS5Modal.Footer {...bs5Props}>{children}</BS5Modal.Footer>}
+    />
+  )
+}

+ 10 - 0
services/web/frontend/stylesheets/bootstrap-5/abstracts/mixins.scss

@@ -52,3 +52,13 @@
   border: 0;
   appearance: none;
 }
+
+@mixin modal-lg {
+  max-width: 960px;
+}
+@mixin modal-md {
+  max-width: 640px;
+}
+@mixin modal-sm {
+  max-width: 480px;
+}

+ 17 - 0
services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss

@@ -129,6 +129,11 @@ $form-validation-states: (
     'border-color': var(--#{$prefix}form-invalid-border-color),
   ),
 );
+// Close buttons
+// Close button
+$btn-close-color: $content-primary;
+$btn-close-opacity: 1;
+$btn-close-width: 10px;
 
 // Colors
 $primary: $bg-accent-01;
@@ -166,3 +171,15 @@ $headings-margin-bottom: $spacing-05;
 // Horizontal rules
 $hr-margin-y: $spacing-08;
 $hr-border-color: $border-divider;
+
+// Modals
+$modal-content-color: $content-secondary;
+$modal-content-border-color: $border-divider;
+$modal-backdrop-bg: $bg-dark-primary;
+$modal-backdrop-opacity: 0.72;
+$box-shadow: shadow-lg();
+$box-shadow-sm: shadow-lg();
+$box-shadow-lg: shadow-lg();
+$modal-header-padding: $spacing-05 $spacing-06;
+$modal-header-padding-x: $spacing-08;
+$modal-header-padding-y: $spacing-08;

+ 1 - 0
services/web/frontend/stylesheets/bootstrap-5/base/bootstrap.scss

@@ -34,6 +34,7 @@
 @import 'bootstrap-5/scss/tooltip';
 @import 'bootstrap-5/scss/spinners';
 @import 'bootstrap-5/scss/card';
+@import 'bootstrap-5/scss/close';
 
 // Helpers
 @import 'bootstrap-5/scss/helpers';

+ 1 - 0
services/web/frontend/stylesheets/bootstrap-5/components/all.scss

@@ -7,4 +7,5 @@
 @import 'badge';
 @import 'form';
 @import 'input-suggestions';
+@import 'modal';
 @import 'footer';

+ 35 - 0
services/web/frontend/stylesheets/bootstrap-5/components/modal.scss

@@ -0,0 +1,35 @@
+:root {
+  --bs-heading-color: var(--content-primary);
+}
+
+@include media-breakpoint-up(sm) {
+  .modal-dialog {
+    @include modal-md();
+  }
+
+  .modal-sm {
+    @include modal-sm();
+  }
+}
+
+@include media-breakpoint-up(md) {
+  .modal-md {
+    @include modal-md();
+  }
+}
+
+@include media-breakpoint-up(lg) {
+  .modal-lg {
+    @include modal-lg();
+  }
+}
+
+.modal-content {
+  @include shadow-lg();
+}
+
+.modal-header {
+  .btn-close {
+    margin: var(--spacing-00);
+  }
+}

+ 2 - 2
services/web/frontend/stylesheets/bootstrap-5/foundations/elevation.scss

@@ -13,6 +13,6 @@
 // Modals, drawers
 @mixin shadow-lg {
   box-shadow:
-    0 8px 24px rgb(30 37 48 / 16%),
-    0 4px 8px rgb(30 37 48 / 16%);
+    0px 8px 16px 0px rgb(30 37 48 / 12%),
+    0px 4px 6px 0px rgb(30 37 48 / 12%);
 }