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

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

GitOrigin-RevId: 90799125f837742b4887eab762ca9ff84a67e70b
This commit is contained in:
Rebeka Dekany
2024-05-14 17:53:25 +02:00
committed by Copybot
parent 3ef06d57c6
commit ea826658a0
12 changed files with 259 additions and 47 deletions

View File

@@ -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>
)
}

View File

@@ -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>
</>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>}
/>
)
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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';

View File

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

View File

@@ -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);
}
}

View File

@@ -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%);
}