♻️ Create shared notification utility component

This commit is contained in:
Xaviju 2025-02-03 13:11:59 +01:00 committed by Xaviju
parent 9660307f00
commit 5c32ec8cfa
9 changed files with 157 additions and 258 deletions

View file

@ -21,6 +21,7 @@
[app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon* token-status-list]] [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.layout.tab-switcher :refer [tab-switcher*]]
[app.main.ui.ds.notifications.actionable :refer [actionable*]] [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.notifications.toast :refer [toast*]]
[app.main.ui.ds.product.autosaved-milestone :refer [autosaved-milestone*]] [app.main.ui.ds.product.autosaved-milestone :refer [autosaved-milestone*]]
[app.main.ui.ds.product.avatar :refer [avatar*]] [app.main.ui.ds.product.avatar :refer [avatar*]]
@ -54,6 +55,7 @@
:Text text* :Text text*
:TabSwitcher tab-switcher* :TabSwitcher tab-switcher*
:Toast toast* :Toast toast*
:NotificationPill notification-pill*
:Actionable actionable* :Actionable actionable*
:TokenStatusIcon token-status-icon* :TokenStatusIcon token-status-icon*
:Swatch swatch* :Swatch swatch*

View file

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

View file

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

View file

@ -9,37 +9,35 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.style :as stl]) [app.main.style :as stl])
(:require (:require
[app.common.data :as d]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.notifications.shared.notification-pill :refer [notification-pill*]]
[rumext.v2 :as mf])) [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 (def ^:private schema:toast
[:map [:map
[:class {:optional true} :string] [:class {:optional true} :string]
[:level {:optional true} [:type {:optional true} [:maybe [:enum :toast :context]]]
[:maybe [:enum "info" "warning" "error" "success"]]] [:level {:optional true} [:maybe [:enum :info :warning :error :success]]]
[:on-close {:optional true} fn?]]) [:on-close {:optional true} fn?]])
(mf/defc toast* (mf/defc toast*
{::mf/props :obj {::mf/props :obj
::mf/schema schema:toast} ::mf/schema schema:toast}
[{:keys [class level children on-close] :rest props}] [{:keys [class level type children on-close] :rest props}]
(let [class (dm/str (stl/css-case :toast true (let [class (dm/str class " " (stl/css-case :toast true))
:toast-info (= level "info") level (if (string? level)
:toast-warning (= level "warning") (keyword level)
:toast-error (= level "error") (d/nilv level :info))
:toast-success (= level "success")) " " class) type (or type :context)
icon-id (or (get icons-by-level level) i/msg-neutral) props (mf/spread-props props {:class class
props (mf/spread-props props {:class class})] :role "alert"
:aria-live "polite"})]
[:> "aside" props [:> "aside" props
[:* [:> notification-pill* {:level level :type type} children]
[:> i/icon* {:icon-id icon-id :class (stl/css :icon)}]
children
;; TODO: this should be a buttom from the DS, but this variant is not designed yet. ;; TODO: this should be a buttom from the DS, but this variant is not designed yet.
;; https://tree.taiga.io/project/penpot/task/8492 ;; 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}]]]))

View file

@ -7,72 +7,33 @@
@use "../_sizes.scss" as *; @use "../_sizes.scss" as *;
@use "../_borders.scss" as *; @use "../_borders.scss" as *;
@use "../typography.scss" as *; @use "../typography.scss" as *;
@use "../spacing.scss" as *;
@use "../z-index.scss" as *;
.toast { .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-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; min-inline-size: $sz-224;
max-inline-size: $sz-480; 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; display: block;
grid-template-columns: auto 1fr auto; position: fixed;
column-gap: var(--sp-s); inset-block-start: var(--toast-inset-block-start-position);
align-items: flex-start; inset-inline-end: var(--toast-inset-inline-end-position);
z-index: var(--toast-vertical-index);
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);
} }
.close-button { .close-button {
appearance: none; appearance: none;
width: $sz-16; width: $sz-16;
height: $sz-16; height: $sz-16;
display: inline-grid; position: absolute;
place-content: center; top: var(--sp-l);
right: var(--sp-l);
background: none;
border: none; border: none;
background: var(--toast-bg-color);
color: var(--toast-icon-color); color: var(--toast-icon-color);
} }

View file

@ -6,6 +6,7 @@
import * as React from "react"; import * as React from "react";
import Components from "@target/components"; import Components from "@target/components";
import { action } from "@storybook/addon-actions";
const { Toast } = Components; const { Toast } = Components;
@ -19,13 +20,12 @@ export default {
}, },
args: { args: {
children: "Lorem ipsum", children: "Lorem ipsum",
onClose: () => { type: "toast",
alert("Close callback"); onClose: action("on-close"),
},
}, },
parameters: { parameters: {
controls: { controls: {
exclude: ["onClose"], exclude: ["onClose", "type"],
}, },
}, },
render: ({ ...args }) => <Toast {...args} />, render: ({ ...args }) => <Toast {...args} />,

View file

@ -8,9 +8,9 @@
(:require (:require
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.store :as st] [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.context-notification :refer [context-notification]]
[app.main.ui.notifications.inline-notification :refer [inline-notification]] [app.main.ui.notifications.inline-notification :refer [inline-notification]]
[app.main.ui.notifications.toast-notification :refer [toast-notification]]
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -26,16 +26,16 @@
inline? (or (= :inline (:type notification)) inline? (or (= :inline (:type notification))
(= :floating (:position notification))) (= :floating (:position notification)))
toast? (or (= :toast (:type notification)) toast? (or (= :toast (:type notification))
(some? (:timeout notification)))] (some? (:timeout notification)))
content (or (:content notification) "")]
(when notification (when notification
(cond (cond
toast? toast?
[:& toast-notification [:> toast*
{:level (or (:level notification) :info) {:level (or (:level notification) :info)
:links (:links notification) :type (:type notification)
:on-close on-close :on-close on-close} content]
:content (:content notification)}]
inline? inline?
[:& inline-notification [:& inline-notification
@ -51,8 +51,7 @@
:content (:content notification)}] :content (:content notification)}]
:else :else
[:& toast-notification [:> toast*
{:level (or (:level notification) :info) {:level (or (:level notification) :info)
:links (:links notification) :type (:type notification)
:on-close on-close :on-close on-close} content]))))
:content (:content notification)}]))))

View file

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

View file

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