Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
feat: Toast 컴포넌트 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
nangkyeonglim committed Aug 14, 2023
1 parent f6bd011 commit ee3534a
Showing 1 changed file with 118 additions and 0 deletions.
118 changes: 118 additions & 0 deletions frontend/src/components/@common/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { AnimationEventHandler, MouseEventHandler, useState } from 'react';
import { styled } from 'styled-components';
import AnimationDiv from 'components/@common/AnimationDiv/AnimationDiv';
import { CloseIcon } from 'assets/icons';
import { linearToRight } from 'styles/animation';

export const ToastTheme = ['light', 'colored'] as const;
export type ToastTheme = (typeof ToastTheme)[number];

export const ToastType = ['plain', 'info', 'success', 'warning', 'error'] as const;
export type ToastType = (typeof ToastType)[number];

type HideToast = MouseEventHandler<HTMLButtonElement> & AnimationEventHandler<HTMLDivElement>;

export type Props = {
toastId: number;
theme?: ToastTheme;
type?: ToastType;
duration?: number;
hasCloseButton?: boolean;
hasProgressBar?: boolean;
message?: string;
onClose: (toastId: number) => void;
};

const Toast = ({
toastId,
theme = 'light',
type = 'plain',
duration = 3000,
hasCloseButton = false,
hasProgressBar = false,
message,
onClose,
}: Props) => {
const [isVisible, setIsVisible] = useState(true);

const hideToast: HideToast = () => setIsVisible(false);

const removeToast =
(toastId: number): AnimationEventHandler<HTMLDivElement> =>
() => {
onClose(toastId);
};

return (
<AnimationDiv isVisible={isVisible} onDisappear={removeToast(toastId)}>
<S.ToastContainer $theme={theme} $type={type}>
<S.Content>
<span>{message}</span>
{hasCloseButton && (
<S.CloseButton onClick={hideToast}>
<CloseIcon width={16} height={16} />
</S.CloseButton>
)}
</S.Content>
<S.ProgressBar $type={type} $hasProgressBar={hasProgressBar}>
<S.PercentageBar $type={type} $duration={duration} onAnimationEnd={hideToast} />
</S.ProgressBar>
</S.ToastContainer>
</AnimationDiv>
);
};

export default Toast;

const S = {
ToastContainer: styled.div<{
$theme: ToastTheme;
$type: ToastType;
}>`
position: relative;
width: fit-content;
padding: 1.2rem 2.4rem;
background-color: ${({ theme, $type, $theme }) =>
$theme === 'light' ? theme.color.gray1 : theme.toastColor[$type].background};
border: 1px solid ${({ theme, $type }) => theme.toastColor[$type].border};
border-radius: 8px;
box-shadow:
#00000014 0px 12px 24px -4px,
rgba(0, 0, 0, 0.04) 0px 8px 16px -4px;
font-size: 1.3rem;
overflow: hidden;
`,
Content: styled.div`
display: flex;
align-items: center;
gap: 1.2rem;
`,
CloseButton: styled.button`
padding: 0.4rem;
border-radius: 4px;
&:hover {
background-color: ${({ theme }) => theme.color.gray5};
}
`,
ProgressBar: styled.div<{ $type: ToastType; $hasProgressBar: boolean }>`
visibility: ${({ $hasProgressBar }) => ($hasProgressBar ? 'visible' : 'hidden')};
content: '';
position: absolute;
bottom: 0;
left: 0;
height: 5px;
width: 100%;
border-radius: 0 0 8px 8px;
background-color: ${({ theme }) => theme.color.gray5};
`,
PercentageBar: styled.div<{ $type: ToastType; $duration: number }>`
height: 100%;
border-radius: 0 0 0 8px;
background-color: ${({ theme, $type }) => theme.toastColor[$type].border};
animation: ${linearToRight} ${({ $duration }) => `${$duration}ms`} linear;
`,
};

0 comments on commit ee3534a

Please sign in to comment.