I'm making a toast notification and trying to implement position options.
My problem is that when I position the toast at the bottom, then add another, the new one appears at the original position, replacing the first one, and the first one is moved down, eventually out of screen.
What I'm trying to achieve is to have the second toast replace the first one but for the first one to then display ABOVE, so they stack upward.
I've made a minimal example on code sandbox
Here is the reactjs component code, where Toasts
is broadly a holder for individual Toast
components:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [toastState, setToastState] = useState([]);
const handleClick = (pos) => {
const ts = [...toastState];
ts.push({ id: ts.length, pos: pos });
setToastState(ts);
};
const Toast = ({ id, pos }) => {
return <div className={`toast ${pos}`}>{id}</div>;
};
const Toasts = () => {
const toasts = [...toastState];
const toastComps = toasts.reverse().map((t, i) => {
return <Toast key={i} id={t.id} pos={t.pos} />;
});
return <div>{toastComps}</div>;
};
return (
<div className="App">
<button
onClick={() => {
handleClick("bottom");
}}
>
Bottom
</button>
<Toasts className="toasts" />
</div>
);
}
And here is the css:
.toasts {
min-width: 100vw;
z-index: 9999;
position: fixed;
top: 0px;
left: 0px;
margin: 0px;
padding: 0px;
pointer-events: none;
}
.toast {
position: relative;
min-height: 50px;
min-width: 20rem;
margin: 5px;
padding: 5px;
border: 1px solid black;
border-radius: 5px;
}
.bottom {
bottom: -80vh;
}
Any suggestions on how to achieve these toast components stacking from bottom up?
I tried out using flexbox, like adding the following properties to the toasts
container class:
display: flex;
flex-direction: column-reverse;
flex-wrap: wrap-reverse;
But to no effect.
I've also tried a js solution, calculating a value for bottom
, based on the toast's index:
const [bot, setBot] = useState(0);
useEffect(() => {
setBot(index ? 80 - (15*index) : 0);
}, [])
//then as an attribute to the `toast`div, after setting className for the position...
style={position.indexOf('bottom') > -1 && bot
? {bottom: `-${bot}vh`} : {}}
This gets closer to what I'm after but when the toasts timeout, or get dismissed, the remaining ones remain high on the screen - so really the whole list is getting pulled up the screen, not stacking upward as intended.
CodePudding user response:
Here is a simplified solution.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [toasts, setToasts] = useState([]);
const btnClicked = () => {
console.log("btnClicked");
setToasts([...toasts, { id: toasts.length }]);
};
return (
<div className="App">
<button onClick={btnClicked}>Bottom</button>
<div className="toasts">
{toasts
.map((toast) => <div className="toast">{toast.id}</div>)
.reverse()}
</div>
</div>
);
}
and the CSS :
.App {
font-family: sans-serif;
text-align: center;
}
.toasts {
position: fixed;
min-width: 100vw;
z-index: 9999;
position: fixed;
bottom: 0px;
left: 0px;
margin: 0px;
padding: 0px;
pointer-events: none;
overflow: hidden;
}
.toast {
position: relative;
min-height: 50px;
min-width: 10rem;
max-width: 10rem;
margin: 5px;
padding: 5px;
border: 1px solid black;
border-radius: 5px;
}
.bottom {
bottom: -80vh;
}
You can see it in action here : CodeSandbox