mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 15:25:10 +08:00
552cf56afe
- more subtle animation when showing a toast - resumes auto close when removing the mouse from the toast - correctly follows reduced motion - uses output with role status as element: https://web.dev/articles/building/a-toast-component - shows toasts inside a section element - prevents toast to all have the same width - fixes a bug on mobile where we would limit the width and the close button wouldn't show correctly aligned I would prefer to have tests for this, but the conjunction of css/animations and our helper changing `discourseLater` to 0 in tests is making it quite challenging for a rather low value. We have system specs using toasts ensuring they show when they should.
89 lines
2.3 KiB
Plaintext
89 lines
2.3 KiB
Plaintext
import Component from "@glimmer/component";
|
|
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;
|
|
|
|
<template>
|
|
<section class="fk-d-toasts">
|
|
{{#each this.toasts.activeToasts as |toast|}}
|
|
<output
|
|
role={{if toast.options.autoClose "status" "log"}}
|
|
key={{toast.id}}
|
|
class={{concatClass "fk-d-toast" toast.options.class}}
|
|
{{(if
|
|
toast.options.autoClose
|
|
(modifier
|
|
AutoCloseToast close=toast.close duration=toast.options.duration
|
|
)
|
|
)}}
|
|
>
|
|
<toast.options.component
|
|
@data={{toast.options.data}}
|
|
@close={{toast.close}}
|
|
/>
|
|
</output>
|
|
{{/each}}
|
|
</section>
|
|
</template>
|
|
}
|