diff --git a/app/assets/javascripts/float-kit/addon/components/d-toasts.gjs b/app/assets/javascripts/float-kit/addon/components/d-toasts.gjs index 2e05e5ff8a5..da18f4e4296 100644 --- a/app/assets/javascripts/float-kit/addon/components/d-toasts.gjs +++ b/app/assets/javascripts/float-kit/addon/components/d-toasts.gjs @@ -1,27 +1,88 @@ import Component from "@glimmer/component"; -import { on } from "@ember/modifier"; +import { registerDestructor } from "@ember/destroyable"; +import { cancel } from "@ember/runloop"; import { inject as service } from "@ember/service"; +import Modifier from "ember-modifier"; import concatClass from "discourse/helpers/concat-class"; +import discourseLater from "discourse-common/lib/later"; +import { bind } from "discourse-common/utils/decorators"; + +const CSS_TRANSITION_DELAY_MS = 300; +const TRANSITION_CLASS = "-fade-out"; + +class AutoCloseToast extends Modifier { + element; + close; + duration; + transitionLaterHandler; + closeLaterHandler; + + constructor(owner, args) { + super(owner, args); + + registerDestructor(this, (instance) => instance.cleanup()); + } + + modify(element, _, { close, duration }) { + this.element = element; + this.close = close; + this.duration = duration; + this.element.addEventListener("mouseenter", this.stopTimer, { + passive: true, + }); + this.element.addEventListener("mouseleave", this.startTimer, { + passive: true, + }); + this.startTimer(); + } + + @bind + startTimer() { + this.transitionLaterHandler = discourseLater(() => { + this.element.classList.add(TRANSITION_CLASS); + + this.closeLaterHandler = discourseLater(() => { + this.close(); + }, CSS_TRANSITION_DELAY_MS); + }, this.duration); + } + + @bind + stopTimer() { + cancel(this.transitionLaterHandler); + cancel(this.closeLaterHandler); + } + + cleanup() { + this.stopTimer(); + this.element.removeEventListener("mouseenter", this.stopTimer); + this.element.removeEventListener("mouseleave", this.startTimer); + } +} export default class DToasts extends Component { @service toasts; } diff --git a/app/assets/javascripts/float-kit/addon/lib/constants.js b/app/assets/javascripts/float-kit/addon/lib/constants.js index f66b722ca1d..6590e1820d8 100644 --- a/app/assets/javascripts/float-kit/addon/lib/constants.js +++ b/app/assets/javascripts/float-kit/addon/lib/constants.js @@ -70,7 +70,6 @@ import DDefaultToast from "float-kit/components/d-default-toast"; export const TOAST = { options: { autoClose: true, - forceAutoClose: false, duration: 3000, component: DDefaultToast, }, diff --git a/app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js index d5f62069e09..367e24ff440 100644 --- a/app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js +++ b/app/assets/javascripts/float-kit/addon/lib/d-toast-instance.js @@ -1,38 +1,14 @@ import { setOwner } from "@ember/application"; import { action } from "@ember/object"; -import { cancel } from "@ember/runloop"; import { inject as service } from "@ember/service"; -import { modifier } from "ember-modifier"; import uniqueId from "discourse/helpers/unique-id"; -import discourseLater from "discourse-common/lib/later"; import { TOAST } from "float-kit/lib/constants"; -const CSS_TRANSITION_DELAY_MS = 500; -const TRANSITION_CLASS = "-fade-out"; - export default class DToastInstance { @service toasts; options = null; id = uniqueId(); - autoCloseHandler = null; - - registerAutoClose = modifier((element) => { - let innerHandler; - - this.autoCloseHandler = discourseLater(() => { - element.classList.add(TRANSITION_CLASS); - - innerHandler = discourseLater(() => { - this.close(); - }, CSS_TRANSITION_DELAY_MS); - }, this.options.duration || TOAST.options.duration); - - return () => { - cancel(innerHandler); - cancel(this.autoCloseHandler); - }; - }); constructor(owner, options = {}) { setOwner(this, owner); @@ -43,14 +19,4 @@ export default class DToastInstance { close() { this.toasts.close(this); } - - @action - cancelAutoClose() { - if (this.options.forceAutoClose) { - // Return early so that we do not cancel the autoClose timer. - return; - } - - cancel(this.autoCloseHandler); - } } diff --git a/app/assets/javascripts/float-kit/addon/services/toasts.js b/app/assets/javascripts/float-kit/addon/services/toasts.js index 533aaf1d63c..5dc05449723 100644 --- a/app/assets/javascripts/float-kit/addon/services/toasts.js +++ b/app/assets/javascripts/float-kit/addon/services/toasts.js @@ -15,7 +15,6 @@ export default class Toasts extends Service { * @param {Object} [options] - options passed to the toast component as `@toast` argument * @param {String} [options.duration] - The duration (ms) of the toast, will be closed after this time * @param {Boolean} [options.autoClose=true] - When true, the toast will autoClose after the duration - * @param {Boolean} [options.forceAutoClose=false] - When true, toast will still autoClose following mouseover event * @param {ComponentClass} [options.component] - A component to render, will use `DDefaultToast` if not provided * @param {String} [options.class] - A class added to the d-toast element * @param {Object} [options.data] - An object which will be passed as the `@data` argument to the component diff --git a/app/assets/stylesheets/common/float-kit/d-default-toast.scss b/app/assets/stylesheets/common/float-kit/d-default-toast.scss index 3be87e4e43e..ec664ac730b 100644 --- a/app/assets/stylesheets/common/float-kit/d-default-toast.scss +++ b/app/assets/stylesheets/common/float-kit/d-default-toast.scss @@ -1,7 +1,6 @@ .fk-d-default-toast { display: flex; flex: 1 1 auto; - max-width: 350px; padding: 0.5rem; &__close-container { diff --git a/app/assets/stylesheets/common/float-kit/d-toasts.scss b/app/assets/stylesheets/common/float-kit/d-toasts.scss index 4ad05435dff..f3c785982af 100644 --- a/app/assets/stylesheets/common/float-kit/d-toasts.scss +++ b/app/assets/stylesheets/common/float-kit/d-toasts.scss @@ -1,16 +1,12 @@ @keyframes d-toast-opening { - 0% { - transform: translateX(0px); - } - 50% { - transform: translateX(-5px); - } - 100% { - transform: translateX(0px); + from { + transform: translateY(var(--transform-y, 10px)); } } .fk-d-toasts { + --transform-y: 0; + position: fixed; top: 5px; right: 5px; @@ -18,6 +14,7 @@ display: flex; flex-direction: column; gap: 5px 0; + flex: 1 1 auto; .mobile-view & { left: 5px; @@ -34,7 +31,12 @@ box-shadow: var(--shadow-menu-panel); overflow-wrap: break-word; display: flex; - animation: d-toast-opening 0.5s ease-in-out; + animation: d-toast-opening 0.3s ease-in-out; + will-change: transform; + + .desktop-view & { + margin-left: auto; + } &:hover { border-color: var(--primary-300); @@ -49,3 +51,9 @@ } } } + +@media (prefers-reduced-motion: no-preference) { + .fk-d-toasts { + --transform-y: 2vh; + } +}