From a3c54c73696e2baa080141068ebb1e5e9bd0f997 Mon Sep 17 00:00:00 2001
From: Jessica Lawshe <5312836+lawshe@users.noreply.github.com>
Date: Wed, 13 Sep 2023 07:31:56 -0500
Subject: [PATCH] Merge pull request #14627 from overleaf/jel-new-alerts
[web] New notification styles
GitOrigin-RevId: ad8a102bbe1ab24be3fccc061f5bbf54912c77e4
---
.../js/shared/components/notification.tsx | 104 ++++++
.../frontend/stories/notification.stories.tsx | 317 ++++++++++++++++++
.../frontend/stories/style-guide.stories.js | 51 ++-
.../stylesheets/app/project-list.less | 26 --
.../stylesheets/components/alerts.less | 18 -
.../stylesheets/components/notifications.less | 183 ++++++++++
.../frontend/stylesheets/core/variables.less | 8 +
.../shared/components/notification.test.tsx | 54 +++
8 files changed, 715 insertions(+), 46 deletions(-)
create mode 100644 services/web/frontend/js/shared/components/notification.tsx
create mode 100644 services/web/frontend/stories/notification.stories.tsx
create mode 100644 services/web/test/frontend/shared/components/notification.test.tsx
diff --git a/services/web/frontend/js/shared/components/notification.tsx b/services/web/frontend/js/shared/components/notification.tsx
new file mode 100644
index 0000000000..aa36ff8940
--- /dev/null
+++ b/services/web/frontend/js/shared/components/notification.tsx
@@ -0,0 +1,104 @@
+import classNames from 'classnames'
+import React, { ReactElement, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import MaterialIcon from './material-icon'
+
+type NotificationType = 'info' | 'success' | 'warning' | 'error'
+
+type NotificationProps = {
+ action?: React.ReactElement
+ ariaLive?: 'polite' | 'off' | 'assertive'
+ content: React.ReactElement
+ customIcon?: React.ReactElement
+ isDismissible?: boolean
+ isActionBelowContent?: boolean
+ onDismiss?: () => void
+ title?: string
+ type: NotificationType
+}
+
+function NotificationIcon({
+ notificationType,
+ customIcon,
+}: {
+ notificationType: NotificationType
+ customIcon?: ReactElement
+}) {
+ let icon =
+ {title} +
+ )} + {content} +The CTA will always go below the content on small screens.
++ We can also opt to always put the CTA below the content on all + screens +
+ + } + isDismissible + isActionBelowContent + /> + ) +} + +export const NotificationWithTitle = (args: Args) => { + return+ Lorem ipsum +
++ Dolor sit amet, consectetur adipiscing elit. Proin lacus velit, + faucibus vitae feugiat sit amet, Some link iaculis + ut mi. +
++ Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum + iaculis eu non diam phasellus. +
+Aliquam at tempor risus. Vestibulum bibendum ut
+ + } + /> + ) +} + +export const NotificationWithMultipleParagraphsAndDismissible = ( + args: Args +) => { + return ( ++ Lorem ipsum +
++ Dolor sit amet, consectetur adipiscing elit. Proin lacus velit, + faucibus vitae feugiat sit amet, Some link iaculis + ut mi. +
++ Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum + iaculis eu non diam phasellus. +
+Aliquam at tempor risus. Vestibulum bibendum ut
+ + } + /> + ) +} + +export const MultipleParagraphsAndAction = (args: Args) => { + return ( ++ Lorem ipsum +
++ Dolor sit amet, consectetur adipiscing elit. Proin lacus velit, + faucibus vitae feugiat sit amet, Some link iaculis + ut mi. +
++ Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum + iaculis eu non diam phasellus. +
+Aliquam at tempor risus. Vestibulum bibendum ut
+ + } + /> + ) +} + +export const MultipleParagraphs = (args: Args) => { + return ( ++ Lorem ipsum +
++ Dolor sit amet, consectetur adipiscing elit. Proin lacus velit, + faucibus vitae feugiat sit amet, Some link iaculis + ut mi. +
++ Vel eros donec ac odio tempor orci dapibus ultrices in. Fermentum + iaculis eu non diam phasellus. +
+Aliquam at tempor risus. Vestibulum bibendum ut
+ + } + /> + ) +} + +export const ShortText = (args: Args) => { + return ( +Success! You made a successful request.
} + /> + ) + + if (isSuccess) return successNotification + return startNotification +} + +export default { + title: 'Shared / Components / Notification', + component: Notification, + args: { + content: ( +
+ This can be any HTML passed to the component. For example,
+ paragraphs, headers, code samples
, links,
+ etc are all supported.
+
See Notification in shared components for options
+.notitifcation .notification-type-info
+
+ .notitifcation .notification-type-success
+
+
+ }
+ />
+ .notitifcation .notification-type-warning
+
+
+ }
+ />
+
+ .notitifcation .notification-type-error
+
+
+ }
+ />
+
+ Note: these styles below will be deprecated since there are new
+ alert styles rolling out as part of the new design system
+
.alert-danger
alert
diff --git a/services/web/frontend/stylesheets/app/project-list.less b/services/web/frontend/stylesheets/app/project-list.less
index 8e6ec67bfb..1a461e59b5 100644
--- a/services/web/frontend/stylesheets/app/project-list.less
+++ b/services/web/frontend/stylesheets/app/project-list.less
@@ -253,32 +253,6 @@ input.project-list-table-select-item[type='checkbox'] {
}
}
}
-.notification-body {
- flex-grow: 1;
- width: 90%;
- @media (min-width: @screen-sm-min) {
- width: auto;
- }
-}
-
-.notification-action {
- margin-top: (@line-height-computed / 2); // match paragraph padding
- order: 1;
- @media (min-width: @screen-sm-min) {
- margin-top: 0;
- order: 0;
- padding-left: @padding-sm;
- }
-}
-
-.notification-close {
- padding-left: @padding-sm;
- text-align: right;
- width: 10%;
- @media (min-width: @screen-sm-min) {
- width: auto;
- }
-}
ul.folders-menu {
margin: @folders-menu-margin;
diff --git a/services/web/frontend/stylesheets/components/alerts.less b/services/web/frontend/stylesheets/components/alerts.less
index 8e6c045e3f..e84a12d6e4 100755
--- a/services/web/frontend/stylesheets/components/alerts.less
+++ b/services/web/frontend/stylesheets/components/alerts.less
@@ -116,21 +116,3 @@
text-decoration: none;
}
}
-
-.design-system {
- .alert {
- display: flex;
- flex-direction: row;
-
- .icon {
- flex: 1 1 auto;
- padding: 0 16px 0 4px;
- }
- }
- .alert-info {
- background-color: @blue-10;
- border: 1px solid @blue-20;
- border-radius: @border-radius-base-new;
- color: @content-primary;
- }
-}
diff --git a/services/web/frontend/stylesheets/components/notifications.less b/services/web/frontend/stylesheets/components/notifications.less
index 0c90b82563..18ac669655 100644
--- a/services/web/frontend/stylesheets/components/notifications.less
+++ b/services/web/frontend/stylesheets/components/notifications.less
@@ -1,3 +1,186 @@
+.notification-body {
+ // will be deprecated once notifications moved to use .notification (see below)
+ flex-grow: 1;
+ width: 90%;
+ @media (min-width: @screen-sm-min) {
+ width: auto;
+ }
+}
+
+.notification-action {
+ // will be deprecated once notifications moved to use .notification (see below)
+ margin-top: (@line-height-computed / 2); // match paragraph padding
+ order: 1;
+ @media (min-width: @screen-sm-min) {
+ margin-top: 0;
+ order: 0;
+ padding-left: @padding-sm;
+ }
+}
+
+.notification-close {
+ // will be deprecated once notifications moved to use .notification (see below)
+ padding-left: @padding-sm;
+ 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(@neutral-90, 0.08);
+ color: @text-color;
+ }
+ }
+
+ @media (min-width: @screen-sm-min) {
+ width: auto;
+ }
+}
+
+.notification {
+ border-radius: @border-radius-base-new;
+ color: @content-primary;
+ display: flex;
+ padding: 0 16px 0 16px; // vertical padding added by elements within notification
+ width: 100%;
+
+ a:not(.btn) {
+ text-decoration: underline;
+ }
+
+ p {
+ margin-bottom: 4px;
+ }
+
+ .notification-icon {
+ flex-grow: 0;
+ padding: 18px 16px 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: 16px 0 16px 0;
+ width: 100%;
+ }
+
+ .notification-cta {
+ padding-bottom: 16px;
+
+ a {
+ font-weight: 700;
+ }
+
+ a,
+ button {
+ white-space: nowrap;
+ }
+ }
+
+ .notification-close-btn {
+ height: 56px;
+ align-items: center;
+ display: flex;
+ }
+
+ .notification-close-btn {
+ padding: 0 0 0 16px;
+
+ 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(@neutral-90, 0.08);
+ color: @text-color;
+ }
+ }
+ }
+
+ &.notification-type-info {
+ background-color: @blue-10;
+ border: 1px solid @blue-20;
+ .notification-icon {
+ color: @blue-50;
+ }
+ }
+ &.notification-type-success {
+ background-color: @green-10;
+ border: 1px solid @green-20;
+ .notification-icon {
+ color: @green-50;
+ }
+ }
+ &.notification-type-warning {
+ background-color: @yellow-10;
+ border: 1px solid @yellow-20;
+ .notification-icon {
+ color: @yellow-40;
+ }
+ }
+ &.notification-type-error {
+ background-color: @red-10;
+ border: 1px solid @red-20;
+ .notification-icon {
+ color: @red-50;
+ }
+ }
+
+ @media (min-width: @screen-sm-min) {
+ &:not(.notification-cta-below-content) {
+ .notification-content-and-cta {
+ flex-wrap: nowrap;
+ }
+
+ .notification-content {
+ width: auto;
+ }
+
+ .notification-cta {
+ height: 56px;
+ padding-left: 16px;
+ padding-bottom: 0;
+ align-items: center;
+ display: flex;
+ }
+ }
+ }
+}
+
+.notification-list {
+ .notification {
+ margin-bottom: @margin-md;
+ }
+}
+
+// Reconfirmation notification
+
.reconfirm-notification {
display: flex;
width: 100%;
diff --git a/services/web/frontend/stylesheets/core/variables.less b/services/web/frontend/stylesheets/core/variables.less
index 9fc342595d..75db0a2bcd 100644
--- a/services/web/frontend/stylesheets/core/variables.less
+++ b/services/web/frontend/stylesheets/core/variables.less
@@ -40,16 +40,24 @@
@blue-20: #c3d0e3;
@blue-30: #97b6e5;
@blue-40: #6597e0;
+@blue-50: #3265b2;
@blue-70: #214475;
@blueDark: #040d2d;
@green: #46a546;
@green-10: #ebf6ea;
+@green-20: #bbdbb8;
@green-50: #138a07;
@green-70: #1f5919;
@green-30: #8cca86;
@red: #a93529;
+@red-10: #f9f1f1;
+@red-20: #f5beba;
+@red-50: #b83a33;
@yellow: #a1a729;
@yellow-10: #fcf1e3;
+@yellow-20: #fcc483;
+@yellow-30: #f7a445;
+@yellow-40: #de8014;
@yellow-50: #8f5514;
@orange: #f89406;
@orange-dark: #9e5e04;
diff --git a/services/web/test/frontend/shared/components/notification.test.tsx b/services/web/test/frontend/shared/components/notification.test.tsx
new file mode 100644
index 0000000000..d7f8077271
--- /dev/null
+++ b/services/web/test/frontend/shared/components/notification.test.tsx
@@ -0,0 +1,54 @@
+import { expect } from 'chai'
+import { screen, render } from '@testing-library/react'
+import Notification from '../../../../frontend/js/shared/components/notification'
+import * as eventTracking from '../../../../frontend/js/infrastructure/event-tracking'
+import sinon from 'sinon'
+
+describe('