Skip to content

Commit 8e6db6f

Browse files
lgrin-bytesikkzz
authored andcommitted
feat: 토스트 기능 구현
1 parent 82fa4f6 commit 8e6db6f

File tree

7 files changed

+123
-5
lines changed

7 files changed

+123
-5
lines changed

src/components/ReviewResult/ReviewResult.module.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
}
3535

3636
.Bottom {
37+
display: flex;
38+
flex-direction: column;
39+
gap: 1.25rem;
40+
}
41+
42+
.ButtonBox {
3743
align-items: center;
3844
gap: 0.875rem;
3945
z-index: 1;

src/components/ReviewResult/ReviewResult.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useEffect } from "react";
22

33
import confetti from "canvas-confetti";
4+
import type { Options as ConfettiOptions } from "canvas-confetti";
5+
6+
import Toast from "../ui/Toast/Toast";
47

58
import HomeNavigateConfirmModal from "@/components/HomeNavigateConfirmModal/HomeNavigateConfirmModal";
69
import styles from "@/components/ReviewResult/ReviewResult.module.scss";
@@ -9,11 +12,15 @@ import IconButton from "@/components/ui/IconButton/IconButton";
912
import Text from "@/components/ui/Text/Text";
1013

1114
import { useOverlay } from "@/hooks/common/useOverlay";
12-
13-
import type { Options as ConfettiOptions } from "canvas-confetti";
15+
import useToast from "@/hooks/common/useToast";
1416

1517
const ReviewResult = () => {
1618
const { isOpen, handleClose, handleOpen } = useOverlay();
19+
const { isToast, showToast } = useToast(1000); // 1초 후 사라짐
20+
21+
const handleCopy = () => {
22+
showToast();
23+
};
1724

1825
const handleConfetti = () => {
1926
const setting: ConfettiOptions = {
@@ -51,12 +58,16 @@ const ReviewResult = () => {
5158
거예요.
5259
</Text>
5360
<div className={styles.IconBtn}>
54-
<IconButton text="복사하기" iconName="paste" size="sm" />
61+
<IconButton text="복사하기" iconName="paste" size="sm" onClick={handleCopy} />
5562
</div>
5663
</div>
64+
5765
<div className={styles.Bottom}>
58-
<Button text="다시생성" variant="secondary" />
59-
<Button text="홈으로 가기" onClick={handleOpen} />
66+
{isToast && <Toast text="링크가 복사되었어요." />}
67+
<div className={styles.ButtonBox}>
68+
<Button text="다시생성" variant="secondary" />
69+
<Button text="홈으로 가기" onClick={handleOpen} />
70+
</div>
6071
</div>
6172

6273
<HomeNavigateConfirmModal isOpen={isOpen} handleClose={handleClose} />
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.ToastStory {
2+
display: flex;
3+
align-items: center;
4+
gap: 4rem;
5+
6+
.Wrapper {
7+
display: flex;
8+
flex-direction: column;
9+
align-items: center;
10+
gap: 1rem;
11+
12+
.InnerWrapper {
13+
width: 1.5rem;
14+
height: 1.5rem;
15+
display: flex;
16+
justify-content: center;
17+
align-items: center;
18+
}
19+
}
20+
}
21+
22+
.Toast {
23+
width: 100%;
24+
height: 3.25rem;
25+
z-index: 1;
26+
border-radius: 0.75rem;
27+
background-color: white;
28+
padding: 0.875rem 1.125rem;
29+
30+
transform: translateY(-20px);
31+
transition:
32+
opacity 0.3s,
33+
transform 0.3s;
34+
}
35+
36+
.show {
37+
opacity: 1;
38+
transform: translateY(0);
39+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Toast from "@/components/ui/Toast/Toast";
2+
3+
import type { Meta, StoryObj } from "@storybook/react";
4+
5+
type ToastProps = {
6+
text: string;
7+
};
8+
9+
const meta: Meta<typeof Toast> = {
10+
title: "Example/Toast",
11+
component: Toast,
12+
};
13+
14+
export default meta;
15+
16+
export const Primary: StoryObj<ToastProps> = {
17+
args: {
18+
text: "링크가 복사되었어요.",
19+
},
20+
};

src/components/ui/Toast/Toast.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { forwardRef } from "react";
2+
3+
import classNames from "classnames";
4+
5+
import Text from "@/components/ui/Text/Text";
6+
import styles from "@/components/ui/Toast/Toast.module.scss";
7+
8+
export interface ToastProps extends React.HTMLAttributes<HTMLDivElement> {
9+
text: string;
10+
}
11+
12+
const Toast = forwardRef<HTMLDivElement, ToastProps>(({ text, className, ...props }, ref) => {
13+
return (
14+
<div ref={ref} className={classNames(styles.Toast, className)} {...props}>
15+
<Text variant="bodyM">{text}</Text>
16+
</div>
17+
);
18+
});
19+
20+
export default Toast;

src/hooks/common/useToast.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useState } from "react";
2+
3+
interface UseToastReturn {
4+
isToast: boolean;
5+
showToast: () => void;
6+
}
7+
8+
const useToast = (duration: number = 1000): UseToastReturn => {
9+
const [isToast, setIsToast] = useState<boolean>(false);
10+
11+
const showToast = () => {
12+
setIsToast(true);
13+
setTimeout(() => {
14+
setIsToast(false);
15+
}, duration);
16+
};
17+
18+
return { isToast, showToast };
19+
};
20+
21+
export default useToast;

src/pages/ReviewResultPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Navbar from "@/components/common/Navbar/Navbar";
44
import CreateReviewLoading from "@/components/CreateReviewLoading/CreateReviewLoading";
55
import ReviewResult from "@/components/ReviewResult/ReviewResult";
66
import Icon from "@/components/ui/Icon/Icon";
7+
import Toast from "@/components/ui/Toast/Toast";
78

89
import { useRoute } from "@/hooks/common/useRoute";
910

0 commit comments

Comments
 (0)