diff --git a/frontend/src/app/main/ui/ds.cljs b/frontend/src/app/main/ui/ds.cljs index 1da41f26c..d30c77969 100644 --- a/frontend/src/app/main/ui/ds.cljs +++ b/frontend/src/app/main/ui/ds.cljs @@ -21,6 +21,7 @@ [app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon* token-status-list]] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.ds.notifications.actionable :refer [actionable*]] + [app.main.ui.ds.notifications.shared.notification-pill :refer [notification-pill*]] [app.main.ui.ds.notifications.toast :refer [toast*]] [app.main.ui.ds.product.autosaved-milestone :refer [autosaved-milestone*]] [app.main.ui.ds.product.avatar :refer [avatar*]] @@ -54,6 +55,7 @@ :Text text* :TabSwitcher tab-switcher* :Toast toast* + :NotificationPill notification-pill* :Actionable actionable* :TokenStatusIcon token-status-icon* :Swatch swatch* diff --git a/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.cljs b/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.cljs new file mode 100644 index 000000000..b86afb6ac --- /dev/null +++ b/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.cljs @@ -0,0 +1,42 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.ds.notifications.shared.notification-pill + (:require-macros + [app.main.style :as stl]) + (:require + [app.main.ui.ds.foundations.assets.icon :as i] + [rumext.v2 :as mf])) + +(defn icons-by-level + [level] + (case level + :info i/info + :warning i/msg-neutral + :error i/delete-text + :success i/status-tick + i/info)) + +(def ^:private schema:notification-pill + [:map + [:level [:enum :info :warning :error :success]] + [:type [:enum :toast :context]]]) + +(mf/defc notification-pill* + {::mf/props :obj + ::mf/schema schema:notification-pill} + [{:keys [level type children]}] + (let [class (stl/css-case :notification-pill true + :type-toast (= type :toast) + :type-context (= type :context) + :level-warning (= level :warning) + :level-error (= level :error) + :level-success (= level :success) + :level-info (= level :info)) + icon-id (icons-by-level level)] + [:div {:class class} + [:> i/icon* {:icon-id icon-id :class (stl/css :icon)}] + children])) diff --git a/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.scss b/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.scss new file mode 100644 index 000000000..290ec44e7 --- /dev/null +++ b/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.scss @@ -0,0 +1,68 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) KALEIDOS INC + +@use "../../_sizes.scss" as *; +@use "../../_borders.scss" as *; +@use "../../typography.scss" as *; + +.notification-pill { + @include use-typography("body-medium"); + + --notification-bg-color: var(--color-background-primary); + --notification-fg-color: var(--color-foreground-primary); + --notification-border-color: var(--color-background-quaternary); + --notification-padding: var(--sp-l); + --notification-icon-color: var(--color-foreground-secondary); + --notification-icon-margin: var(--sp-xxs); + + background-color: var(--notification-bg-color); + border: $b-1 solid var(--notification-border-color); + border-radius: $br-8; + padding: var(--notification-padding); + + display: flex; + gap: var(--sp-s); + + color: var(--notification-fg-color); +} + +.type-toast { + padding-inline-end: var(--sp-xxxl); +} + +.level-info { + --notification-bg-color: var(--color-background-info); + --notification-fg-color: var(--color-foreground-primary); + --notification-border-color: var(--color-accent-info); + --notification-icon-color: var(--color-accent-info); +} + +.level-error { + --notification-bg-color: var(--color-background-error); + --notification-fg-color: var(--color-foreground-primary); + --notification-border-color: var(--color-accent-error); + --notification-icon-color: var(--color-accent-error); +} + +.level-warning { + --notification-bg-color: var(--color-background-warning); + --notification-fg-color: var(--color-foreground-warning); + --notification-border-color: var(--color-accent-warning); + --notification-icon-color: var(--color-accent-warning); +} + +.level-success { + --notification-bg-color: var(--color-background-success); + --notification-fg-color: var(--color-foreground-success); + --notification-border-color: var(--color-accent-success); + --notification-icon-color: var(--color-accent-success); +} + +.icon { + flex-shrink: 0; + color: var(--notification-icon-color); + margin-block-start: var(--notification-icon-margin); +} diff --git a/frontend/src/app/main/ui/ds/notifications/toast.cljs b/frontend/src/app/main/ui/ds/notifications/toast.cljs index 84f582c08..2dfad3352 100644 --- a/frontend/src/app/main/ui/ds/notifications/toast.cljs +++ b/frontend/src/app/main/ui/ds/notifications/toast.cljs @@ -9,37 +9,35 @@ [app.common.data.macros :as dm] [app.main.style :as stl]) (:require + [app.common.data :as d] [app.main.ui.ds.foundations.assets.icon :as i] + [app.main.ui.ds.notifications.shared.notification-pill :refer [notification-pill*]] [rumext.v2 :as mf])) -(def ^:private icons-by-level - {"info" i/info - "warning" i/msg-neutral - "error" i/delete-text - "success" i/status-tick}) - (def ^:private schema:toast [:map [:class {:optional true} :string] - [:level {:optional true} - [:maybe [:enum "info" "warning" "error" "success"]]] + [:type {:optional true} [:maybe [:enum :toast :context]]] + [:level {:optional true} [:maybe [:enum :info :warning :error :success]]] [:on-close {:optional true} fn?]]) (mf/defc toast* {::mf/props :obj ::mf/schema schema:toast} - [{:keys [class level children on-close] :rest props}] - (let [class (dm/str (stl/css-case :toast true - :toast-info (= level "info") - :toast-warning (= level "warning") - :toast-error (= level "error") - :toast-success (= level "success")) " " class) - icon-id (or (get icons-by-level level) i/msg-neutral) - props (mf/spread-props props {:class class})] + [{:keys [class level type children on-close] :rest props}] + (let [class (dm/str class " " (stl/css-case :toast true)) + level (if (string? level) + (keyword level) + (d/nilv level :info)) + type (or type :context) + props (mf/spread-props props {:class class + :role "alert" + :aria-live "polite"})] [:> "aside" props - [:* - [:> i/icon* {:icon-id icon-id :class (stl/css :icon)}] - children + [:> notification-pill* {:level level :type type} children] ;; TODO: this should be a buttom from the DS, but this variant is not designed yet. ;; https://tree.taiga.io/project/penpot/task/8492 - [:> "button" {:on-click on-close :aria-label "Close" :class (stl/css :close-button)} [:> i/icon* {:icon-id i/close}]]]])) + [:> "button" {:on-click on-close + :aria-label "Close" + :class (stl/css :close-button)} + [:> i/icon* {:icon-id i/close}]]])) diff --git a/frontend/src/app/main/ui/ds/notifications/toast.scss b/frontend/src/app/main/ui/ds/notifications/toast.scss index 5eec0f83c..44f4edf95 100644 --- a/frontend/src/app/main/ui/ds/notifications/toast.scss +++ b/frontend/src/app/main/ui/ds/notifications/toast.scss @@ -7,72 +7,33 @@ @use "../_sizes.scss" as *; @use "../_borders.scss" as *; @use "../typography.scss" as *; +@use "../spacing.scss" as *; +@use "../z-index.scss" as *; .toast { - @include use-typography("body-medium"); - - --toast-bg-color: var(--color-background-primary); - --toast-fg-color: var(--color-foreground-primary); - --toast-border-color: var(--color-background-quaternary); - --toast-padding: var(--sp-l); --toast-icon-color: var(--color-foreground-secondary); - --toast-icon-margin: var(--sp-xxs); + --toast-vertical-index: var(--z-index-notifications); + --toast-inset-block-start-position: var(--sp-l); + --toast-inset-inline-end-position: var(--sp-l); min-inline-size: $sz-224; max-inline-size: $sz-480; - background-color: var(--toast-bg-color); - border: $b-1 solid var(--toast-border-color); - border-radius: $br-8; - padding: var(--toast-padding); - display: inline-grid; - grid-template-columns: auto 1fr auto; - column-gap: var(--sp-s); - align-items: flex-start; - - color: var(--toast-fg-color); -} - -.toast-info { - --toast-bg-color: var(--color-background-info); - --toast-fg-color: var(--color-foreground-primary); - --toast-border-color: var(--color-accent-info); - --toast-icon-color: var(--color-accent-info); -} - -.toast-error { - --toast-bg-color: var(--color-background-error); - --toast-fg-color: var(--color-foreground-primary); - --toast-border-color: var(--color-accent-error); - --toast-icon-color: var(--color-accent-error); -} - -.toast-warning { - --toast-bg-color: var(--color-background-warning); - --toast-fg-color: var(--color-foreground-primary); - --toast-border-color: var(--color-accent-warning); - --toast-icon-color: var(--color-accent-warning); -} - -.toast-success { - --toast-bg-color: var(--color-background-success); - --toast-fg-color: var(--color-foreground-primary); - --toast-border-color: var(--color-accent-success); - --toast-icon-color: var(--color-accent-success); -} - -.icon { - color: var(--toast-icon-color); - margin-block-start: var(--toast-icon-margin); + display: block; + position: fixed; + inset-block-start: var(--toast-inset-block-start-position); + inset-inline-end: var(--toast-inset-inline-end-position); + z-index: var(--toast-vertical-index); } .close-button { appearance: none; width: $sz-16; height: $sz-16; - display: inline-grid; - place-content: center; + position: absolute; + top: var(--sp-l); + right: var(--sp-l); + background: none; border: none; - background: var(--toast-bg-color); color: var(--toast-icon-color); } diff --git a/frontend/src/app/main/ui/ds/notifications/toast.stories.jsx b/frontend/src/app/main/ui/ds/notifications/toast.stories.jsx index 91f702743..7afeb2d1b 100644 --- a/frontend/src/app/main/ui/ds/notifications/toast.stories.jsx +++ b/frontend/src/app/main/ui/ds/notifications/toast.stories.jsx @@ -6,6 +6,7 @@ import * as React from "react"; import Components from "@target/components"; +import { action } from "@storybook/addon-actions"; const { Toast } = Components; @@ -19,13 +20,12 @@ export default { }, args: { children: "Lorem ipsum", - onClose: () => { - alert("Close callback"); - }, + type: "toast", + onClose: action("on-close"), }, parameters: { controls: { - exclude: ["onClose"], + exclude: ["onClose", "type"], }, }, render: ({ ...args }) => , diff --git a/frontend/src/app/main/ui/notifications.cljs b/frontend/src/app/main/ui/notifications.cljs index 6eb0fda86..43087580c 100644 --- a/frontend/src/app/main/ui/notifications.cljs +++ b/frontend/src/app/main/ui/notifications.cljs @@ -8,9 +8,9 @@ (:require [app.main.data.notifications :as ntf] [app.main.store :as st] + [app.main.ui.ds.notifications.toast :refer [toast*]] [app.main.ui.notifications.context-notification :refer [context-notification]] [app.main.ui.notifications.inline-notification :refer [inline-notification]] - [app.main.ui.notifications.toast-notification :refer [toast-notification]] [okulary.core :as l] [rumext.v2 :as mf])) @@ -26,16 +26,16 @@ inline? (or (= :inline (:type notification)) (= :floating (:position notification))) toast? (or (= :toast (:type notification)) - (some? (:timeout notification)))] + (some? (:timeout notification))) + content (or (:content notification) "")] (when notification (cond toast? - [:& toast-notification + [:> toast* {:level (or (:level notification) :info) - :links (:links notification) - :on-close on-close - :content (:content notification)}] + :type (:type notification) + :on-close on-close} content] inline? [:& inline-notification @@ -51,8 +51,7 @@ :content (:content notification)}] :else - [:& toast-notification + [:> toast* {:level (or (:level notification) :info) - :links (:links notification) - :on-close on-close - :content (:content notification)}])))) + :type (:type notification) + :on-close on-close} content])))) diff --git a/frontend/src/app/main/ui/notifications/toast_notification.cljs b/frontend/src/app/main/ui/notifications/toast_notification.cljs deleted file mode 100644 index 0879042dc..000000000 --- a/frontend/src/app/main/ui/notifications/toast_notification.cljs +++ /dev/null @@ -1,71 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.main.ui.notifications.toast-notification - (:require-macros [app.main.style :as stl]) - (:require - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.main.ui.components.link-button :as lb] - [app.main.ui.icons :as i] - [rumext.v2 :as mf])) - -(def ^:private neutral-icon - (i/icon-xref :msg-neutral (stl/css :icon))) - -(def ^:private error-icon - (i/icon-xref :delete-text (stl/css :icon))) - -(def ^:private success-icon - (i/icon-xref :status-tick (stl/css :icon))) - -(def ^:private info-icon - (i/icon-xref :help (stl/css :icon))) - -(def ^:private close-icon - (i/icon-xref :close (stl/css :close-icon))) - -(defn get-icon-by-level - [level] - (case level - :warning neutral-icon - :error error-icon - :success success-icon - :info info-icon - neutral-icon)) - -(mf/defc toast-notification - "These are ephemeral elements that disappear when the close button - is pressed, the page is refreshed, the page is navigated to another - page or after 7 seconds, which is enough time to be read, except for - error messages that require user interaction." - - {::mf/props :obj} - [{:keys [level content on-close links] :as props}] - - [:aside {:class (stl/css-case :toast-notification true - :warning (= level :warning) - :error (= level :error) - :success (= level :success) - :info (= level :info))} - - (get-icon-by-level level) - - [:div {:class (stl/css :text)} - content - (when (some? links) - [:nav {:class (stl/css :link-nav)} - (for [[index link] (d/enumerate links)] - [:& lb/link-button {:key (dm/str "link-" index) - :class (stl/css :link) - :on-click (:callback link) - :value (:label link)}])])] - - - - [:button {:class (stl/css :btn-close) - :on-click on-close} - close-icon]]) diff --git a/frontend/src/app/main/ui/notifications/toast_notification.scss b/frontend/src/app/main/ui/notifications/toast_notification.scss deleted file mode 100644 index 6626fc119..000000000 --- a/frontend/src/app/main/ui/notifications/toast_notification.scss +++ /dev/null @@ -1,100 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) KALEIDOS INC - -@import "refactor/common-refactor.scss"; - -.toast-notification { - --toast-notification-bg-color: var(--alert-background-color-default); - --toast-notification-fg-color: var(--alert-text-foreground-color-default); - --toast-notification-icon-color: var(--alert-icon-foreground-color-default); - --toast-notification-border-color: var(--alert-border-color-default); - @include alertShadow; - position: fixed; - top: $s-16; - right: $s-16; - display: grid; - grid-template-columns: $s-16 1fr auto; - gap: $s-8; - min-height: $s-32; - min-width: $s-228; - max-width: $s-400; - padding: $s-8; - border: $s-1 solid var(--toast-notification-border-color); - background-color: var(--toast-notification-bg-color); - border-radius: $br-8; - color: var(--toast-notification-fg-color); - z-index: $z-index-alert; -} - -.warning { - --toast-notification-bg-color: var(--alert-background-color-warning); - --toast-notification-fg-color: var(--alert-text-foreground-color-warning); - --toast-notification-icon-color: var(--alert-icon-foreground-color-warning); - --toast-notification-border-color: var(--alert-border-color-warning); -} - -.success { - --toast-notification-bg-color: var(--alert-background-color-success); - --toast-notification-fg-color: var(--alert-text-foreground-color-success); - --toast-notification-icon-color: var(--alert-icon-foreground-color-success); - --toast-notification-border-color: var(--alert-border-color-success); -} - -.info { - --toast-notification-bg-color: var(--alert-background-color-info); - --toast-notification-fg-color: var(--alert-text-foreground-color-info); - --toast-notification-icon-color: var(--alert-icon-foreground-color-info); - --toast-notification-border-color: var(--alert-border-color-info); -} - -.default { - --toast-notification-bg-color: var(--alert-background-color-default); - --toast-notification-fg-color: var(--alert-text-foreground-color-default); - --toast-notification-icon-color: var(--alert-icon-foreground-color-default); - --toast-notification-border-color: var(--alert-border-color-default); -} - -.error { - --toast-notification-bg-color: var(--alert-background-color-error); - --toast-notification-fg-color: var(--alert-text-foreground-color-error); - --toast-notification-icon-color: var(--alert-icon-foreground-color-error); - --toast-notification-border-color: var(--alert-border-color-error); -} - -.link-nav { - display: inline; -} - -.link { - @include bodySmallTypography; - color: var(--modal-link-foreground-color); - margin: 0; -} - -.icon { - @extend .button-icon; - align-self: flex-start; - stroke: var(--toast-notification-icon-color); -} - -.text { - @include bodySmallTypography; - align-self: center; -} - -.btn-close { - @include buttonStyle; - align-self: flex-start; - width: $s-16; - margin: 0; - padding: 0; - background-color: transparent; -} - -.close-icon { - @extend .button-icon; - stroke: var(--toast-notification-icon-color); -}