Stacked toast view

From the previous commit, we are now showing toasts in the reverse order (latest to oldest).

Next, extend the "hide snack" timer to handle multiple snacks. It will dismiss them one by one, restarting itself until no more toasts.

Show a stacked GUI when there are multiple toasts.

Users can still manually dismiss the toasts.
This commit is contained in:
infinite-persistence 2022-05-06 18:08:09 +08:00 committed by Thomas Zarebczan
parent 55916f0763
commit dae78c488f
4 changed files with 50 additions and 15 deletions

View file

@ -1,14 +1,15 @@
import { connect } from 'react-redux';
import { doDismissToast } from 'redux/actions/notifications';
import { selectToast } from 'redux/selectors/notifications';
import { selectToast, selectToastCount } from 'redux/selectors/notifications';
import SnackBar from './view';
const perform = dispatch => ({
const perform = (dispatch) => ({
removeSnack: () => dispatch(doDismissToast()),
});
const select = state => ({
const select = (state) => ({
snack: selectToast(state),
snackCount: selectToastCount(state),
});
export default connect(select, perform)(SnackBar);

View file

@ -9,40 +9,46 @@ import LbcMessage from 'component/common/lbc-message';
type Props = {
removeSnack: (any) => void,
snack: ?ToastParams,
snackCount: number,
};
class SnackBar extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
this.hideTimeout = null;
this.intervalId = null;
}
hideTimeout: ?TimeoutID;
intervalId: ?IntervalID;
render() {
const { snack, removeSnack } = this.props;
const { snack, snackCount, removeSnack } = this.props;
if (!snack) {
this.hideTimeout = null; // should be unmounting anyway, but be safe?
clearInterval(this.intervalId);
this.intervalId = null; // should be unmounting anyway, but be safe?
return null;
}
const { message, subMessage, duration, linkText, linkTarget, isError } = snack;
if (this.hideTimeout === null) {
this.hideTimeout = setTimeout(
if (this.intervalId) {
// TODO: render should be pure
clearInterval(this.intervalId);
}
this.intervalId = setInterval(
() => {
this.hideTimeout = null;
removeSnack();
},
duration === 'long' ? 15000 : 5000
duration === 'long' ? 10000 : 5000
);
}
return (
<div
className={classnames('snack-bar', {
'snack-bar--error': isError,
'snack-bar--stacked-error': snackCount > 1 && isError,
'snack-bar--stacked-non-error': snackCount > 1 && !isError,
})}
>
<div className="snack-bar__message">

View file

@ -41,6 +41,8 @@ export const selectToast = createSelector(selectState, (state) => {
return null;
});
export const selectToastCount = (state) => selectState(state).toasts.length;
export const selectError = createSelector(selectState, (state) => {
if (state.errors.length) {
const { error } = state.errors[0];

View file

@ -37,6 +37,32 @@
background-color: var(--color-snack-bg-error);
}
.snack-bar--stacked-non-error {
box-shadow: // The top layer shadow
0 1px 1px var(--color-border),
// The second layer
0 10px 0 -5px var(--color-primary),
// The second layer shadow
0 10px 1px -4px var(--color-border);
//// The third layer
//0 20px 0 -10px var(--color-primary),
//// The third layer shadow
//0 20px 1px -9px var(--color-border);
}
.snack-bar--stacked-error {
box-shadow: // The top layer shadow
0 1px 1px var(--color-border),
// The second layer
0 10px 0 -5px var(--color-snack-bg-error),
// The second layer shadow
0 10px 1px -4px var(--color-border);
//// The third layer
//0 20px 0 -10px var(--color-snack-bg-error),
//// The third layer shadow
//0 20px 1px -9px var(--color-border);
}
.snack-bar--notification {
@extend .card;
background-color: var(--color-card-background);