반응형
참고
- https://mui.com/material-ui/react-alert/
- https://mui.com/base-ui/react-snackbar/
이벤트가 발생하면 플로팅 메시지(토스트 메시지)가 나타나고 사라지도록 해보자. (링크에서 확인)
구현
showAlert는 플로팅 메시지를 보여주는 조건이다.
const [showAlert, setShowAlert] = useState(false);
버튼을 클릭하면 N번째 메시지를 보여준다고 가정하자.
const handleButtonClick = (index) => {
setShowAlert(true);
setFloatingMessage(`${index} 번째 플로팅 메시지...`);
};
임의의 여러 내용은 아래와 같이 만들었다.
해당 내용 옆에 플로팅 메시지를 만드는 버튼이 있다.
const paragraphs = Array.from({ length: 30 }, (_, index) => (
<p key={index}>
{` ${index} : 번째 내용 ........................................................................................................................................................................................................ `}
<Button
variant="outlined"
color="primary"
onClick={() => handleButtonClick(index)}
>
Button
</Button>
</p>
));
Mui의 Alert를 아래와 같이 설정하자.
화면의 가운데, 그리고 위에서 바로 아래(top : 5)에 메시지가 나타나도록 한다.
<Alert
severity="info"
onClose={handleAlertClose}
style={{
position: "fixed",
top: 5,
left: "50%",
transform: "translateX(-50%)",
zIndex: 9999,
opacity: 0.8, // 투명도 조절
}}
>
{floatingMessage}
</Alert>
useEffect를 이용해 3초 뒤에 메시지가 자동으로 사라지도록 설정할 수 있다.
useEffect(() => {
let timer;
if (showAlert) {
timer = setTimeout(() => {
setShowAlert(false);
}, 3000);
}
return () => clearTimeout(timer);
}, [showAlert]);
전체 코드는 다음과 같다.
import React, { useState, useEffect } from "react";
import { Alert } from "@material-ui/lab";
import { Button } from "@material-ui/core";
const Floating = () => {
const [showAlert, setShowAlert] = useState(false);
const [floatingMessage, setFloatingMessage] = useState("");
useEffect(() => {
let timer;
if (showAlert) {
timer = setTimeout(() => {
setShowAlert(false);
}, 3000);
}
return () => clearTimeout(timer);
}, [showAlert]);
const handleButtonClick = (index) => {
setShowAlert(true);
setFloatingMessage(`${index} 번째 플로팅 메시지...`);
};
const handleAlertClose = () => {
setShowAlert(false);
};
const paragraphs = Array.from({ length: 30 }, (_, index) => (
<p key={index}>
{` ${index} : 번째 내용 ........................................................................................................................................................................................................ `}
<Button
variant="outlined"
color="primary"
onClick={() => handleButtonClick(index)}
>
Button
</Button>
</p>
));
return (
<div>
{paragraphs}
{showAlert && (
<Alert
severity="info"
onClose={handleAlertClose}
style={{
position: "fixed",
top: 5,
left: "50%",
transform: "translateX(-50%)",
zIndex: 9999,
opacity: 0.8, // 투명도 조절
}}
>
{floatingMessage}
</Alert>
)}
</div>
);
};
export default Floating;
참고 : 스낵바를 이용할 수도 있다.
const StyledSnackbar = styled(Snackbar)`
position: fixed;
z-index: 5500;
display: flex;
bottom: 16px;
right: 16px;
`;
styled로 설정된 값을 잘 수정하면 위치를 변경할 수 있다.
const StyledSnackbar = styled(Snackbar)`
position: fixed;
z-index: 5500;
display: flex;
top: 16px;
left: 50%;
transform: translateX(-50%);
`;
전체 코드는 다음과 같다.
import React, { useState, useEffect, useRef } from "react";
import { Alert } from "@material-ui/lab";
import { Button } from "@material-ui/core";
import { Transition } from "react-transition-group";
import { styled } from "@mui/system";
import { Snackbar } from "@mui/base/Snackbar";
import CloseIcon from "@mui/icons-material/Close";
const blue = {
200: "#99CCF3",
300: "#66B2FF",
400: "#3399FF",
500: "#007FFF",
600: "#0072E5",
700: "#0066CC",
};
const grey = {
50: "#F3F6F9",
100: "#E5EAF2",
200: "#DAE2ED",
300: "#C7D0DD",
400: "#B0B8C4",
500: "#9DA8B7",
600: "#6B7A90",
700: "#434D5B",
800: "#303740",
900: "#1C2025",
};
const TriggerButton = styled("button")(
({ theme }) => `
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 600;
font-size: 0.875rem;
line-height: 1.5;
padding: 8px 16px;
border-radius: 8px;
color: white;
transition: all 150ms ease;
cursor: pointer;
background: ${theme.palette.mode === "dark" ? grey[900] : "#fff"};
border: 1px solid ${theme.palette.mode === "dark" ? grey[700] : grey[200]};
color: ${theme.palette.mode === "dark" ? grey[200] : grey[900]};
box-shadow: 0 1px 2px ${
theme.palette.mode === "dark"
? "rgba(0, 0, 0, 0.5)"
: "rgba(45, 45, 60, 0.15)"
};
&:hover {
background: ${theme.palette.mode === "dark" ? grey[800] : grey[50]};
border-color: ${theme.palette.mode === "dark" ? grey[600] : grey[300]};
}
&:active {
background: ${theme.palette.mode === "dark" ? grey[700] : grey[100]};
}
&:focus-visible {
box-shadow: 0 0 0 4px ${
theme.palette.mode === "dark" ? blue[300] : blue[200]
};
outline: none;
}
`
);
const StyledSnackbar = styled(Snackbar)`
position: fixed;
z-index: 5500;
display: flex;
top: 16px;
left: 50%;
transform: translateX(-50%);
`;
const SnackbarContent = styled("div")(
({ theme }) => `
position: relative;
overflow: hidden;
z-index: 5500;
display: flex;
left: auto;
justify-content: space-between;
max-width: 560px;
min-width: 300px;
background-color: ${theme.palette.mode === "dark" ? grey[900] : "#fff"};
border-radius: 8px;
border: 1px solid ${theme.palette.mode === "dark" ? grey[700] : grey[200]};
box-shadow: ${
theme.palette.mode === "dark"
? `0 2px 8px rgb(0 0 0 / 0.5)`
: `0 2px 8px ${grey[200]}`
};
padding: 0.75rem;
color: ${theme.palette.mode === "dark" ? grey[50] : grey[900]};
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 600;
& .snackbar-title {
margin-right: 0.5rem;
}
& .snackbar-close-icon {
cursor: pointer;
font-size: 10px;
width: 1.25rem;
height: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
}
`
);
const positioningStyles = {
entering: "translateX(0)",
entered: "translateX(0)",
exiting: "translateX(1500px)",
exited: "translateX(1500px)",
unmounted: "translateX(1500px)",
};
const Floating = () => {
const [showAlert, setShowAlert] = useState(false);
const [floatingMessage, setFloatingMessage] = useState("");
const [open, setOpen] = useState(false);
const [exited, setExited] = useState(true);
const nodeRef = useRef(null);
const handleClose = (_, reason) => {
if (reason === "clickaway") {
return;
}
setOpen(false);
};
const handleClick = () => {
setOpen(true);
};
const handleOnEnter = () => {
setExited(false);
};
const handleOnExited = () => {
setExited(true);
};
useEffect(() => {
let timer;
if (showAlert) {
timer = setTimeout(() => {
setShowAlert(false);
}, 3000);
}
return () => clearTimeout(timer);
}, [showAlert]);
const handleButtonClick = (index) => {
setShowAlert(true);
setFloatingMessage(`${index} 번째 플로팅 메시지...`);
};
const handleAlertClose = () => {
setShowAlert(false);
};
const paragraphs = Array.from({ length: 30 }, (_, index) => (
<p key={index}>
{` ${index} : 번째 내용 ........................................................................................................................................................................................................ `}
<Button
variant="outlined"
color="primary"
onClick={() => handleButtonClick(index)}
>
Button
</Button>
</p>
));
return (
<div>
<div>
<TriggerButton type="button" onClick={handleClick}>
Open snackbar
</TriggerButton>
<StyledSnackbar
autoHideDuration={5000}
open={open}
onClose={handleClose}
exited={exited}
>
<Transition
timeout={{ enter: 400, exit: 400 }}
in={open}
appear
unmountOnExit
onEnter={handleOnEnter}
onExited={handleOnExited}
nodeRef={nodeRef}
>
{(status) => (
<SnackbarContent
style={{
transform: positioningStyles[status],
transition: "transform 300ms ease",
}}
ref={nodeRef}
>
<div className="snackbar-title">Hello World</div>
<CloseIcon
onClick={handleClose}
className="snackbar-close-icon"
/>
</SnackbarContent>
)}
</Transition>
</StyledSnackbar>
</div>
{paragraphs}
{showAlert && (
<Alert
severity="info"
onClose={handleAlertClose}
style={{
position: "fixed",
top: 5,
left: "50%",
transform: "translateX(-50%)",
zIndex: 9999,
opacity: 0.8, // 투명도 조절
}}
>
{floatingMessage}
</Alert>
)}
</div>
);
};
export default Floating;
반응형
'개발 > React' 카테고리의 다른 글
리액트 - crypto-js로 문자열 암호화/복호화하기 (Encrypting/Decrypting String) (0) | 2024.02.28 |
---|---|
리액트 - module.css로 CSS 스타일 관리하기 (0) | 2024.02.21 |
리액트 Material - 런타임에 Tab 추가, 삭제하기 (Mui Dynamic Tabs) (0) | 2024.02.03 |
리액트 - react-diff-viewer-continued로 텍스트 비교하기 (Compare Text with Diff Viewer) (1) | 2024.01.31 |
리액트 - html2pdf로 PDF 다운로드하기 (0) | 2024.01.28 |
댓글