Просмотр исходного кода

Merge pull request #17576 from overleaf/ii-bs5-alert

[web] Bootstrap 5 notifications

GitOrigin-RevId: 4409f1b76923d96f1b8297beb35a383d9aa7ec8c
ilkin-overleaf 2 лет назад
Родитель
Сommit
bd570cc473

+ 9 - 5
services/web/frontend/js/features/settings/components/emails-section.tsx

@@ -9,7 +9,7 @@ import EmailsHeader from './emails/header'
 import EmailsRow from './emails/row'
 import AddEmail from './emails/add-email'
 import Icon from '../../../shared/components/icon'
-import { Alert } from 'react-bootstrap'
+import NotificationWrapper from '@/features/ui/components/bootstrap-5/notification-wrapper'
 import { ExposedSettings } from '../../../../../types/exposed-settings'
 import { LeaversSurveyAlert } from './leavers-survey-alert'
 
@@ -67,10 +67,14 @@ function EmailsSectionContent() {
         {isInitializingSuccess && <LeaversSurveyAlert />}
         {isInitializingSuccess && !hideAddSecondaryEmail && <AddEmail />}
         {isInitializingError && (
-          <Alert bsStyle="danger" className="text-center">
-            <Icon type="exclamation-triangle" fw />{' '}
-            {t('error_performing_request')}
-          </Alert>
+          <NotificationWrapper
+            type="error"
+            content={t('error_performing_request')}
+            bs3Props={{
+              icon: <Icon type="exclamation-triangle" fw />,
+              className: 'text-center',
+            }}
+          />
         )}
       </>
     </>

+ 42 - 0
services/web/frontend/js/features/ui/components/bootstrap-5/notification-wrapper.tsx

@@ -0,0 +1,42 @@
+import Notification from '@/shared/components/notification'
+import { Alert, AlertProps } from 'react-bootstrap'
+import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
+import classnames from 'classnames'
+
+type NotificationWrapperProps = React.ComponentProps<typeof Notification> & {
+  bs3Props?: {
+    icon: React.ReactElement
+    className?: string
+  }
+}
+
+function NotificationWrapper(props: NotificationWrapperProps) {
+  const { bs3Props, ...notificationProps } = props
+
+  const alertProps = {
+    // Map `error` to `danger`
+    bsStyle:
+      notificationProps.type === 'error' ? 'danger' : notificationProps.type,
+    className: classnames(notificationProps.className, bs3Props?.className),
+    onDismiss: notificationProps.onDismiss,
+  } as const satisfies AlertProps
+
+  return (
+    <BootstrapVersionSwitcher
+      bs3={
+        <Alert {...alertProps}>
+          {bs3Props?.icon}
+          {bs3Props?.icon && ' '}
+          {notificationProps.content}
+        </Alert>
+      }
+      bs5={
+        <div className="notification-list">
+          <Notification {...notificationProps} />
+        </div>
+      }
+    />
+  )
+}
+
+export default NotificationWrapper

+ 4 - 2
services/web/frontend/stylesheets/bootstrap-5/abstracts/variable-overrides.scss

@@ -4,8 +4,10 @@
 $font-family-sans-serif: 'Noto Sans', sans-serif;
 $font-family-serif: 'Merriweather', serif;
 $font-family-monospace: 'DM Mono', monospace;
-$font-size-base: $default-font-size;
-$line-height-base: $default-line-height;
+
+$font-size-base: 1rem;
+$font-size-sm: var(--font-size-02);
+$line-height-base: 1.5;
 
 // Buttons
 $btn-font-family: $font-family-sans-serif;

+ 10 - 6
services/web/frontend/stylesheets/bootstrap-5/bootstrap.scss

@@ -7,8 +7,8 @@
 // Bootstrap itself because Bootstrap uses them to create the CSS variables it
 // uses, and in calculations to determine, for example, what color text to use
 // on a button based on contrast.
-@import 'abstracts/all';
 @import 'foundations/all';
+@import 'abstracts/all';
 
 // Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
 @import 'bootstrap-5/scss/variables';
@@ -17,21 +17,25 @@
 // Include remainder of required parts
 @import 'bootstrap-5/scss/maps';
 @import 'bootstrap-5/scss/mixins';
-@import 'bootstrap-5/scss/root';
-
-// Include any other optional parts as needed, including components
 @import 'bootstrap-5/scss/utilities';
+
+// Layout & components
+@import 'bootstrap-5/scss/root';
 @import 'bootstrap-5/scss/reboot';
 @import 'bootstrap-5/scss/type';
 @import 'bootstrap-5/scss/images';
 @import 'bootstrap-5/scss/containers';
 @import 'bootstrap-5/scss/grid';
-@import 'bootstrap-5/scss/helpers';
 @import 'bootstrap-5/scss/buttons';
 @import 'bootstrap-5/scss/dropdown';
 @import 'bootstrap-5/scss/modal';
-@import 'bootstrap-5/scss/utilities/api';
 @import 'bootstrap-5/scss/spinners';
 
+// Helpers
+@import 'bootstrap-5/scss/helpers';
+
+// Utilities
+@import 'bootstrap-5/scss/utilities/api';
+
 // Components custom style
 @import 'components/all';

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

@@ -1,3 +1,4 @@
 @import 'button';
 @import 'dropdown-menu';
 @import 'split-button';
+@import 'notifications';

+ 248 - 0
services/web/frontend/stylesheets/bootstrap-5/components/notifications.scss

@@ -0,0 +1,248 @@
+.notification-body {
+  // will be deprecated once notifications moved to use .notification (see below)
+  flex-grow: 1;
+  width: 90%;
+  @media (min-width: var(--bs-breakpoint-md)) {
+    width: auto;
+  }
+}
+
+.notification-action {
+  $line-height-computed: $font-size-base * $line-height-base; // 24px
+
+  // will be deprecated once notifications moved to use .notification (see below)
+  margin-top: calc($line-height-computed / 2); // match paragraph padding
+  order: 1;
+  @media (min-width: var(--bs-breakpoint-md)) {
+    margin-top: 0;
+    order: 0;
+    padding-left: $spacing-05;
+  }
+}
+
+.notification-close {
+  // will be deprecated once notifications moved to use .notification (see below)
+  padding-left: $spacing-05;
+  text-align: right;
+  width: 10%;
+
+  button {
+    aspect-ratio: 1;
+    border-radius: 50%;
+    display: flex;
+    float: right;
+    padding: 5.5px;
+    cursor: pointer;
+    background: transparent;
+    border: 0;
+
+    &:hover,
+    &:focus {
+      background-color: rgba(var(--neutral-90), 0.08);
+      color: var(--content-secondary);
+    }
+  }
+
+  @media (min-width: var(--bs-breakpoint-md)) {
+    width: auto;
+  }
+}
+
+.notification {
+  border-radius: $border-radius-base;
+  color: var(--content-primary);
+  display: flex;
+  padding: 0 $spacing-06; // vertical padding added by elements within notification
+  width: 100%;
+
+  a:not(.btn) {
+    text-decoration: underline;
+  }
+
+  p {
+    margin-bottom: $spacing-02;
+  }
+
+  .notification-icon {
+    flex-grow: 0;
+    padding: 18px $spacing-06 0 0;
+  }
+
+  .notification-content-and-cta {
+    // shared container to align cta with text on smaller screens
+    display: flex;
+    flex-grow: 1;
+    flex-wrap: wrap;
+
+    p:last-child {
+      margin-bottom: 0;
+    }
+  }
+
+  .notification-content {
+    flex-grow: 1;
+    padding: $spacing-06 0;
+    width: 100%;
+  }
+
+  .notification-cta {
+    padding-bottom: $spacing-06;
+
+    a {
+      font-weight: 700;
+    }
+
+    a,
+    button {
+      white-space: nowrap;
+    }
+  }
+
+  .notification-disclaimer {
+    color: var(--neutral-60);
+    font-size: $font-size-sm;
+    padding-bottom: $spacing-06;
+  }
+
+  .notification-close-btn {
+    height: $spacing-12;
+    align-items: center;
+    display: flex;
+  }
+
+  .notification-close-btn {
+    padding: 0 0 0 $spacing-06;
+
+    button {
+      aspect-ratio: 1;
+      border-radius: 50%;
+      display: flex;
+      float: right;
+      padding: 5.5px;
+      cursor: pointer;
+      background: transparent;
+      border: 0;
+
+      &:hover,
+      &:focus {
+        background-color: rgba(var(--neutral-90), 0.08);
+        color: var(--content-secondary);
+      }
+    }
+  }
+
+  &.notification-type-info {
+    background-color: var(--bg-info-03);
+    border: 1px solid var(--blue-20);
+    .notification-icon {
+      color: var(--blue-50);
+    }
+  }
+  &.notification-type-success {
+    background-color: var(--bg-accent-03);
+    border: 1px solid var(--green-20);
+    .notification-icon {
+      color: var(--green-50);
+    }
+  }
+  &.notification-type-warning {
+    background-color: var(--bg-warning-03);
+    border: 1px solid var(--yellow-20);
+    .notification-icon {
+      color: var(--yellow-40);
+    }
+  }
+  &.notification-type-error {
+    background-color: var(--bg-danger-03);
+    border: 1px solid var(--red-20);
+    .notification-icon {
+      color: var(--red-50);
+    }
+  }
+  &.notification-type-offer {
+    background-color: var(--bg-light-primary);
+    border: 1px solid var(--neutral-20);
+    .notification-icon {
+      color: var(--neutral-50);
+    }
+  }
+
+  @media (min-width: var(--bs-breakpoint-md)) {
+    &:not(.notification-cta-below-content) {
+      .notification-content-and-cta {
+        flex-wrap: nowrap;
+      }
+
+      .notification-content {
+        width: auto;
+      }
+
+      .notification-cta {
+        height: $spacing-12;
+        padding-left: $spacing-06;
+        padding-bottom: 0;
+        align-items: center;
+        display: flex;
+      }
+    }
+  }
+}
+
+.notification-with-scroll-margin {
+  scroll-margin: $spacing-06;
+}
+
+.notification-list {
+  .notification {
+    margin-bottom: $spacing-07;
+  }
+}
+
+// Reconfirmation notification
+
+.reconfirm-notification {
+  display: flex;
+  width: 100%;
+  .fa-warning {
+    margin-right: $spacing-05;
+  }
+  .btn-reconfirm {
+    float: right;
+    margin-left: $spacing-05;
+    text-transform: capitalize;
+  }
+}
+
+.group-invitation-cancel-subscription-notification-buttons {
+  display: flex;
+  align-items: center;
+}
+
+// Settings page
+.affiliations-table {
+  .reconfirm-notification {
+    margin: 0 auto $spacing-05 auto !important;
+    padding: $spacing-07;
+  }
+
+  .reconfirm-row {
+    td {
+      border: 0;
+
+      .alert {
+        border: 0;
+        padding: 0;
+      }
+
+      :not(.alert) {
+        .reconfirm-notification {
+          background-color: var(--neutral-10);
+          border-radius: $border-radius-base;
+          .fa-warning {
+            color: var(--yellow-40);
+          }
+        }
+      }
+    }
+  }
+}

+ 0 - 3
services/web/frontend/stylesheets/bootstrap-5/foundations/typography.scss

@@ -1,9 +1,6 @@
 // This file provides CSS variables for font size and line height, plus Sass variables for base text size for Bootstrap
 @use 'sass:math';
 
-$default-font-size: 1rem;
-$default-line-height: 1.5;
-
 :root {
   --font-size-01: 0.75rem; // 12px
   --font-size-02: 0.875rem; // 14px

+ 0 - 7
services/web/frontend/stylesheets/bootstrap-5/main-style.scss

@@ -14,13 +14,6 @@ $is-overleaf-light: false;
 // TODO Bootstrap 5: Check whether this works with Bootstrap 5, and whether we can replace it
 @import '../vendor/select/select';
 
-// Sass and CSS variables from Overleaf foundations
-@import 'foundations/colors';
-@import 'foundations/spacing';
-@import 'foundations/typography';
-@import 'foundations/border-radius';
-@import 'foundations/elevation';
-
 // Boostrap-related
 
 // Note that files containing Bootstrap or Sass files that interact with