Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Toast 구현 #18

Merged
merged 12 commits into from
Nov 7, 2023
82 changes: 82 additions & 0 deletions src/components/Toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Meta, StoryObj } from '@storybook/react';

import { useToast } from '@/hooks/useToast';

import { Toast } from './Toast';
import { ToastDuration } from './Toast.type';

const meta: Meta<typeof Toast> = {
title: 'Component/Toast',
component: Toast,
parameters: {
layout: 'centered',
},
};
export default meta;

const ToastStory = ({ ...toastProps }) => {
return (
<div style={{ display: 'flex', gap: '10px', width: '780px', height: '644px' }}>
<div
style={{
backgroundColor: '#c4c4c4',
width: '50%',
position: 'relative',
}}
>
short duration toast (1.5s)
<Toast {...toastProps} />
</div>
<div
style={{
backgroundColor: '#c4c4c4',
width: '50%',
position: 'relative',
}}
>
long duration toast (3s)
<Toast duration="long" {...toastProps} />
</div>
</div>
);
};

const HookTest = () => {
const toastProps = {
children: 'useToast를 사용한 토스트 메시지',
duration: 'long' as ToastDuration,
};
const { showToast, isShowToast } = useToast();

return (
<div
style={{ backgroundColor: '#c4c4c4', width: '390px', height: '644px', position: 'relative' }}
>
<button
onClick={() => {
showToast(toastProps.duration);
}}
>
버튼을 누르면 토스트가 발생합니다
</button>
{isShowToast && <Toast {...toastProps} />}
</div>
);
};

type Story = StoryObj<typeof Toast>;
export const SingleLine: Story = {
args: {
children: '토스트 메시지',
},
render: ToastStory,
};
export const MultiLine: Story = {
args: {
children: '줄 수가 두 줄 이상이 되는 토스트 메시지입니다. 좌측 정렬을 해주세요.',
},
render: ToastStory,
};
export const ToastHook: Story = {
render: HookTest,
};
59 changes: 59 additions & 0 deletions src/components/Toast/Toast.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { css, keyframes, styled } from 'styled-components';

import { ToastDuration } from './Toast.type';

interface StyledToastProps {
$duration: ToastDuration;
}

const SHORT_DURATION = 1.5;
const LONG_DURATION = 3;
const FADE_DURATION = 0.25;

export const ToastFadeIn = keyframes`
to {
opacity: 1;
}
`;

export const ToastFadeOut = keyframes`
to {
opacity: 0;
}
`;

const setToastAnimation = ($duration: ToastDuration) => {
switch ($duration) {
case 'short':
return css`
${ToastFadeIn} ${FADE_DURATION}s ease-in forwards,
${ToastFadeOut} ${FADE_DURATION}s ${SHORT_DURATION + FADE_DURATION}s ease-out forwards
`;
case 'long':
return css`
${ToastFadeIn} ${FADE_DURATION}s ease-in forwards,
${ToastFadeOut} ${FADE_DURATION}s ${LONG_DURATION + FADE_DURATION}s ease-out forwards
`;
}
};

export const StyledToastWrapper = styled.div`
position: absolute;
bottom: 66px;
width: 100%;
padding: 0px 8px;
`;

export const StyledToast = styled.div<StyledToastProps>`
opacity: 0;
border-radius: 8px;
width: 100%;
padding: 16px 24px;
display: flex;
justify-content: center;
background-color: ${({ theme }) => theme.color.toastBG};
color: ${({ theme }) => theme.color.textBright};
${({ theme }) => theme.typo.body2};

animation: ${({ $duration }) => setToastAnimation($duration)};
`;
14 changes: 14 additions & 0 deletions src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { StyledToast, StyledToastWrapper } from './Toast.style';
import { ToastProps } from './Toast.type';

export const Toast = ({ children, duration = 'short', ...props }: ToastProps) => {
if (!children) return;

return (
<StyledToastWrapper>
<StyledToast $duration={duration} {...props}>
{children}
</StyledToast>
</StyledToastWrapper>
);
};
6 changes: 6 additions & 0 deletions src/components/Toast/Toast.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type ToastDuration = 'short' | 'long';

export interface ToastProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
duration?: ToastDuration;
}
2 changes: 2 additions & 0 deletions src/components/Toast/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Toast } from './Toast';
export type { ToastProps } from './Toast.type';
3 changes: 3 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ export type { PlainButtonProps } from './PlainButton';

export { Toggle } from './Toggle';
export type { ToggleProps } from './Toggle';

export { Toast } from './Toast';
export type { ToastProps } from './Toast';
1 change: 1 addition & 0 deletions src/hooks/useToast/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useToast } from './useToast';
29 changes: 29 additions & 0 deletions src/hooks/useToast/useToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState } from 'react';

import { ToastDuration } from '@/components/Toast/Toast.type';

export const useToast = () => {
const removeTime = {
short: 2000,
nijuy marked this conversation as resolved.
Show resolved Hide resolved
long: 3500,
};

const [isShowToast, setIsShowToast] = useState(false);

const showToast = (duration: ToastDuration) => {
setIsShowToast(true);
removeToast(duration);
};

const removeToast = (duration: ToastDuration) => {
const timer = setTimeout(() => {
setIsShowToast(false);
}, removeTime[duration]);

return () => {
clearTimeout(timer);
};
};

return { isShowToast, showToast };
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type SemanticTextColor =
| 'textSecondary'
| 'textTertiary'
| 'textDisabled'
| 'textBright'
| 'textPointed'
| 'textWarned';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const lightSemanticColorPalette: SemanticColorPalette = {
textSecondary: baseColorPalettes.light.gray900,
textTertiary: baseColorPalettes.light.gray600,
textDisabled: baseColorPalettes.light.gray500,
textBright: baseColorPalettes.light.white000,
textPointed: baseColorPalettes.light.pointColor400,
textWarned: baseColorPalettes.light.warningRed400,

Expand Down Expand Up @@ -141,6 +142,7 @@ const darkSemanticColorPalette: SemanticColorPalette = {
textSecondary: baseColorPalettes.dark.gray800,
textTertiary: baseColorPalettes.dark.gray600,
textDisabled: baseColorPalettes.dark.gray400,
textBright: baseColorPalettes.dark.white000,
textPointed: baseColorPalettes.dark.pointColor400,
textWarned: baseColorPalettes.dark.warningRed400,

Expand Down