From 26a2ef8fb7f5e9939f3f01c1e6f753096de06ae5 Mon Sep 17 00:00:00 2001 From: Xavier Julian Date: Tue, 11 Feb 2025 16:31:21 +0100 Subject: [PATCH] :recycle: Create DS context notification component --- frontend/src/app/main/ui/ds.cljs | 2 + .../main/ui/ds/notifications/actionable.scss | 6 ++ .../context-notification.stories.jsx | 100 ++++++++++++++++++ .../notifications/context_notification.cljs | 53 ++++++++++ .../ds/notifications/context_notification.css | 0 .../shared/notification_pill.cljs | 17 ++- .../shared/notification_pill.scss | 10 ++ .../app/main/ui/ds/notifications/toast.cljs | 25 ++++- .../app/main/ui/ds/notifications/toast.scss | 16 +++ .../ui/ds/notifications/toast.stories.jsx | 11 ++ 10 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 frontend/src/app/main/ui/ds/notifications/context-notification.stories.jsx create mode 100644 frontend/src/app/main/ui/ds/notifications/context_notification.cljs create mode 100644 frontend/src/app/main/ui/ds/notifications/context_notification.css diff --git a/frontend/src/app/main/ui/ds.cljs b/frontend/src/app/main/ui/ds.cljs index d30c77969..abd34f5c1 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.context-notification :refer [context-notification*]] [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*]] @@ -55,6 +56,7 @@ :Text text* :TabSwitcher tab-switcher* :Toast toast* + :ContextNotification context-notification* :NotificationPill notification-pill* :Actionable actionable* :TokenStatusIcon token-status-icon* diff --git a/frontend/src/app/main/ui/ds/notifications/actionable.scss b/frontend/src/app/main/ui/ds/notifications/actionable.scss index ca2b2eb0f..ed11f7b11 100644 --- a/frontend/src/app/main/ui/ds/notifications/actionable.scss +++ b/frontend/src/app/main/ui/ds/notifications/actionable.scss @@ -27,4 +27,10 @@ justify-content: space-between; padding: var(--actionable-padding); padding-inline-start: var(--sp-l); + + // Targets the potential links included by the creator in the children props. + & a { + color: var(--color-accent-primary); + text-decoration: none; + } } diff --git a/frontend/src/app/main/ui/ds/notifications/context-notification.stories.jsx b/frontend/src/app/main/ui/ds/notifications/context-notification.stories.jsx new file mode 100644 index 000000000..14ac864b3 --- /dev/null +++ b/frontend/src/app/main/ui/ds/notifications/context-notification.stories.jsx @@ -0,0 +1,100 @@ +// 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 * as React from "react"; +import Components from "@target/components"; + +const { ContextNotification } = Components; + +export default { + title: "Notifications/ContextNotification", + component: ContextNotification, + argTypes: { + children: { + control: { type: "text" }, + }, + appearance: { + options: ["neutral", "ghost"], + control: { type: "select" }, + }, + level: { + options: ["info", "error", "warning", "success"], + control: { type: "select" }, + }, + }, + args: { + children: "Lorem ipsum", + isHtml: false, + type: "context", + appearance: "neutral", + level: "info", + }, + parameters: { + controls: { + exclude: ["type", "isHtml"], + }, + }, + render: ({ ...args }) => , +}; + +export const Default = {}; + +export const WithLongerText = { + args: { + children: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lorem ante, bibendum sed ex.", + }, + parameters: { + controls: { exclude: ["isHtml"] }, + }, +}; + +export const WithHTML = { + args: { + children: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lorem ante, bibendum sed ex.", + isHtml: true, + }, + parameters: { + controls: { exclude: ["isHtml"] }, + }, +}; + +export const Info = { + args: { + level: "info", + }, + parameters: { + controls: { exclude: ["level", "isHtml"] }, + }, +}; + +export const Error = { + args: { + level: "error", + }, + parameters: { + controls: { exclude: ["level", "isHtml"] }, + }, +}; + +export const Warning = { + args: { + level: "warning", + }, + parameters: { + controls: { exclude: ["level", "isHtml"] }, + }, +}; + +export const Success = { + args: { + level: "success", + }, + parameters: { + controls: { exclude: ["level", "isHtml"] }, + }, +}; diff --git a/frontend/src/app/main/ui/ds/notifications/context_notification.cljs b/frontend/src/app/main/ui/ds/notifications/context_notification.cljs new file mode 100644 index 000000000..fc867f898 --- /dev/null +++ b/frontend/src/app/main/ui/ds/notifications/context_notification.cljs @@ -0,0 +1,53 @@ +;; 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.context-notification + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.main.ui.ds.notifications.shared.notification-pill :refer [notification-pill*]] + [rumext.v2 :as mf])) + +(def ^:private schema:context-notification + [:map + [:class {:optional true} :string] + [:type {:optional true} [:maybe [:enum :toast :context]]] + [:appearance {:optional true} [:enum :neutral :ghost]] + [:level {:optional true} [:maybe [:enum :info :warning :error :success]]] + [:is-html {:optional true} :boolean]]) + +(mf/defc context-notification* + "Persistent notifications, they do not disappear. + These are contextual messages in specific areas of the tool, usually in modals and Dashboard area, and are mainly informative." + {::mf/props :obj + ::mf/schema schema:context-notification} + [{:keys [class type appearance level is-html children] :rest props}] + (let [class (dm/str class " " (stl/css-case :contextual-notification true + :contain-html is-html + :level-warning (= level :warning) + :level-error (= level :error) + :level-success (= level :success) + :level-info (= level :info))) + level (if (string? level) + (keyword level) + (d/nilv level :info)) + type (if (string? type) + (keyword type) + (d/nilv type :context)) + appearance (if (string? appearance) + (keyword appearance) + (d/nilv appearance :neutral)) + is-html (or is-html false) + props (mf/spread-props props {:class class + :role "alert" + :aria-live "polite"})] + [:> "aside" props + [:> notification-pill* {:level level + :type type + :is-html is-html + :appearance appearance} children]])) + diff --git a/frontend/src/app/main/ui/ds/notifications/context_notification.css b/frontend/src/app/main/ui/ds/notifications/context_notification.css new file mode 100644 index 000000000..e69de29bb 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 index b86afb6ac..6fc6064c8 100644 --- a/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.cljs +++ b/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.cljs @@ -23,20 +23,31 @@ (def ^:private schema:notification-pill [:map [:level [:enum :info :warning :error :success]] - [:type [:enum :toast :context]]]) + [:type [:enum :toast :context]] + [:appearance {:optional true} [:enum :neutral :ghost]] + [:is-html {:optional true} :boolean]]) (mf/defc notification-pill* {::mf/props :obj ::mf/schema schema:notification-pill} - [{:keys [level type children]}] + [{:keys [level type is-html appearance children]}] (let [class (stl/css-case :notification-pill true + :appearance-neutral (= appearance :neutral) + :appearance-ghost (= appearance :ghost) :type-toast (= type :toast) :type-context (= type :context) :level-warning (= level :warning) :level-error (= level :error) :level-success (= level :success) :level-info (= level :info)) + is-html (or is-html false) icon-id (icons-by-level level)] [:div {:class class} [:> i/icon* {:icon-id icon-id :class (stl/css :icon)}] - children])) + ;; The content can arrive in markdown format, in these cases + ;; we will use the prop is-html to true to indicate it and + ;; that the html injection is performed and the necessary css classes are applied. + (if is-html + [:div {:class (stl/css :context-text) + :dangerouslySetInnerHTML #js {:__html children}}] + 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 index 290ec44e7..200dd1e6e 100644 --- a/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.scss +++ b/frontend/src/app/main/ui/ds/notifications/shared/notification_pill.scss @@ -27,12 +27,22 @@ gap: var(--sp-s); color: var(--notification-fg-color); + + // Targets the potential links included by the creator in the children props. + & a { + color: var(--color-accent-primary); + text-decoration: none; + } } .type-toast { padding-inline-end: var(--sp-xxxl); } +.appearance-ghost { + background-color: transparent; +} + .level-info { --notification-bg-color: var(--color-background-info); --notification-fg-color: var(--color-foreground-primary); diff --git a/frontend/src/app/main/ui/ds/notifications/toast.cljs b/frontend/src/app/main/ui/ds/notifications/toast.cljs index 2dfad3352..e0ff541c7 100644 --- a/frontend/src/app/main/ui/ds/notifications/toast.cljs +++ b/frontend/src/app/main/ui/ds/notifications/toast.cljs @@ -19,25 +19,40 @@ [:class {:optional true} :string] [:type {:optional true} [:maybe [:enum :toast :context]]] [:level {:optional true} [:maybe [:enum :info :warning :error :success]]] + [:appearance {:optional true} [:enum :neutral :ghost]] + [:is-html {:optional true} :boolean] [:on-close {:optional true} fn?]]) (mf/defc toast* {::mf/props :obj ::mf/schema schema:toast} - [{:keys [class level type children on-close] :rest props}] - (let [class (dm/str class " " (stl/css-case :toast true)) + [{:keys [class level appearance type is-html children on-close] :rest props}] + (let [class (dm/str class " " (stl/css :toast)) level (if (string? level) (keyword level) (d/nilv level :info)) - type (or type :context) + type (if (string? type) + (keyword type) + (d/nilv type :context)) + appearance (if (string? appearance) + (keyword appearance) + (d/nilv appearance :neutral)) + is-html (or is-html false) props (mf/spread-props props {:class class :role "alert" :aria-live "polite"})] [:> "aside" props - [:> notification-pill* {:level level :type type} children] + [:> notification-pill* {:level level + :type type + :is-html is-html + :appearance appearance} 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)} + :class (stl/css-case :close-button true + :level-warning (= level :warning) + :level-error (= level :error) + :level-success (= level :success) + :level-info (= level :info))} [:> 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 44f4edf95..a572aac07 100644 --- a/frontend/src/app/main/ui/ds/notifications/toast.scss +++ b/frontend/src/app/main/ui/ds/notifications/toast.scss @@ -26,6 +26,22 @@ z-index: var(--toast-vertical-index); } +.level-info { + --toast-icon-color: var(--color-accent-info); +} + +.level-error { + --toast-icon-color: var(--color-accent-error); +} + +.level-warning { + --toast-icon-color: var(--color-accent-warning); +} + +.level-success { + --toast-icon-color: var(--color-accent-success); +} + .close-button { appearance: none; width: $sz-16; 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 7afeb2d1b..daf2df4ee 100644 --- a/frontend/src/app/main/ui/ds/notifications/toast.stories.jsx +++ b/frontend/src/app/main/ui/ds/notifications/toast.stories.jsx @@ -40,6 +40,17 @@ export const WithLongerText = { }, }; +export const WithHTML = { + args: { + children: + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent lorem ante, bibendum sed ex.", + isHtml: true, + }, + parameters: { + controls: { exclude: ["isHtml"] }, + }, +}; + export const Info = { args: { level: "info",