-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Jeongwoo Park <[email protected]>
- Loading branch information
1 parent
825bfe3
commit b40ca1e
Showing
12 changed files
with
315 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* eslint-disable max-len */ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import Toast from '.'; | ||
|
||
const meta: Meta<typeof Toast> = { | ||
title: 'Common/Toast', | ||
component: Toast, | ||
parameters: { | ||
layout: 'centered', | ||
docs: { | ||
description: { | ||
component: 'Toast 컴포넌트는 메시지를 화면에 나타내며, type에 따라 스타일이 달라집니다.', | ||
}, | ||
}, | ||
}, | ||
tags: ['autodocs'], | ||
argTypes: { | ||
message: { | ||
description: '표시할 메시지입니다.', | ||
control: { type: 'text' }, | ||
defaultValue: 'This is a toast message', | ||
}, | ||
type: { | ||
description: 'Toast의 타입입니다.', | ||
control: { type: 'select' }, | ||
options: ['default', 'success', 'error', 'primary'], | ||
defaultValue: 'default', | ||
}, | ||
visible: { | ||
description: 'Toast의 렌더링 여부입니다.', | ||
control: { type: 'boolean' }, | ||
defaultValue: true, | ||
}, | ||
}, | ||
args: { | ||
visible: true, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
type Story = StoryObj<typeof Toast>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
message: 'Default Alert!', | ||
type: 'default', | ||
}, | ||
}; | ||
|
||
export const Success: Story = { | ||
args: { | ||
message: 'Success Alert!', | ||
type: 'success', | ||
}, | ||
}; | ||
|
||
export const Error: Story = { | ||
args: { | ||
message: 'Error Alert!', | ||
type: 'error', | ||
}, | ||
}; | ||
|
||
export const Primary: Story = { | ||
args: { | ||
message: 'Primary Alert!', | ||
type: 'primary', | ||
}, | ||
}; | ||
|
||
export const LongAlert: Story = { | ||
args: { | ||
message: 'Primary Alert Primary Alert Primary Alert Primary Alert! ', | ||
type: 'primary', | ||
}, | ||
}; | ||
|
||
export const SuperLongAlert: Story = { | ||
args: { | ||
message: | ||
'Primary Alert Primary Alert Primary Alert Primary Alert! Primary Alert Primary Alert Primary Alert Primary Alert Primary Alert Primary Alert Primary Alert Primary !!! Alert Primary Alert Primary Alert Primary Alert Primary Alert Primary', | ||
type: 'primary', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { ToastType } from '@contexts/ToastContext'; | ||
import { useEffect, useState } from 'react'; | ||
import S from './style'; | ||
|
||
interface ToastProps { | ||
message: string; | ||
type?: ToastType; | ||
visible: boolean; | ||
} | ||
|
||
export default function Toast({ message, type = 'default', visible }: ToastProps) { | ||
return ( | ||
<S.ToastContainer | ||
type={type} | ||
visible={visible} | ||
> | ||
<S.Message>{message}</S.Message> | ||
</S.ToastContainer> | ||
); | ||
} | ||
|
||
export function ToastModal({ message, type = 'default' }: Omit<ToastProps, 'visible'>) { | ||
const [visible, setVisible] = useState(true); | ||
|
||
useEffect(() => { | ||
const timer = setTimeout(() => { | ||
setVisible(false); | ||
}, 3000); | ||
|
||
return () => clearTimeout(timer); | ||
}, []); | ||
|
||
return ( | ||
<Toast | ||
message={message} | ||
type={type} | ||
visible={visible} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/* eslint-disable default-case */ | ||
/* eslint-disable consistent-return */ | ||
import { keyframes } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
|
||
interface ToastContainerProps { | ||
type: 'default' | 'success' | 'error' | 'primary'; | ||
} | ||
|
||
const slideIn = keyframes` | ||
from { | ||
transform: translateY(-20px) translateX(-50%); | ||
opacity: 0; | ||
} | ||
to { | ||
transform: translateY(0) translateX(-50%); | ||
opacity: 1; | ||
} | ||
`; | ||
|
||
const slideOut = keyframes` | ||
from { | ||
transform: translateY(0) translateX(-50%); | ||
opacity: 1; | ||
} | ||
to { | ||
transform: translateY(-20px) translateX(-50%); | ||
opacity: 0; | ||
} | ||
`; | ||
|
||
const ToastContainer = styled.div<ToastContainerProps & { visible: boolean }>` | ||
position: absolute; | ||
top: 5%; | ||
left: 50%; | ||
transform: translate(-50%, -50%); | ||
min-width: 20rem; | ||
max-width: max(50vw, 32rem); | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
width: fit-content; | ||
height: 4.8rem; | ||
padding: 0 1.2rem; | ||
border-radius: 0.8rem; | ||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); | ||
background-color: ${({ type, theme }) => { | ||
switch (type) { | ||
case 'default': | ||
return theme.baseColors.grayscale[50]; | ||
case 'success': | ||
return theme.colors.feedback.success; | ||
case 'error': | ||
return theme.colors.feedback.error; | ||
case 'primary': | ||
return theme.colors.brand.primary; | ||
} | ||
}}; | ||
color: ${({ type, theme }) => | ||
type === 'default' ? theme.baseColors.grayscale[800] : theme.baseColors.grayscale[50]}; | ||
${({ theme }) => theme.typography.common.block} | ||
animation: ${({ visible }) => (visible ? slideIn : slideOut)} 0.5s ease-out; | ||
animation-fill-mode: forwards; | ||
z-index: 1000; | ||
`; | ||
|
||
const Message = styled.div` | ||
white-space: nowrap; | ||
overflow: hidden; | ||
text-overflow: ellipsis; | ||
max-width: 100%; | ||
`; | ||
|
||
const S = { | ||
ToastContainer, | ||
Message, | ||
}; | ||
|
||
export default S; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { ToastModal } from '@components/common/Toast'; | ||
import { createContext, useState, ReactNode, useContext, useMemo, useCallback, useRef } from 'react'; | ||
|
||
type ToastContextType = { | ||
alert: (message: string) => void; | ||
error: (message: string) => void; | ||
success: (message: string) => void; | ||
primary: (message: string) => void; | ||
}; | ||
|
||
export type ToastType = 'default' | 'success' | 'error' | 'primary'; | ||
|
||
const ToastContext = createContext<ToastContextType | null>(null); | ||
|
||
export default function ToastProvider({ children }: { children: ReactNode }) { | ||
const [toastList, setToastList] = useState<{ id: number; type: ToastType; message: string }[]>([]); | ||
const idRef = useRef(0); | ||
|
||
const removeToast = (id: number) => { | ||
setToastList((prev) => prev.filter((toast) => toast.id !== id)); | ||
}; | ||
|
||
const handleAlert = useCallback( | ||
(type: ToastType) => (message: string) => { | ||
const id = idRef.current; | ||
setToastList((prev) => [...prev, { id, type, message }]); | ||
idRef.current += 1; | ||
|
||
setTimeout(() => removeToast(id), 4000); | ||
}, | ||
[], | ||
); | ||
|
||
const providerValue = useMemo( | ||
() => ({ | ||
alert: handleAlert('default'), | ||
error: handleAlert('error'), | ||
success: handleAlert('success'), | ||
primary: handleAlert('primary'), | ||
}), | ||
[handleAlert], | ||
); | ||
|
||
return ( | ||
<ToastContext.Provider value={providerValue}> | ||
{toastList.map(({ id, type, message }) => ( | ||
<ToastModal | ||
type={type} | ||
message={message} | ||
key={id} | ||
/> | ||
))} | ||
{children} | ||
</ToastContext.Provider> | ||
); | ||
} | ||
|
||
export const useToast = () => { | ||
const value = useContext(ToastContext); | ||
|
||
if (!value) { | ||
throw new Error('Toast Context가 존재하지 않습니다.'); | ||
} | ||
|
||
return value; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,9 +15,8 @@ const authHandlers = [ | |
await new Promise((resolve) => setTimeout(resolve, 2000)); | ||
|
||
if (!body.email || !body.password || body.email !== '[email protected]' || body.password !== 'admin') { | ||
return new Response(null, { | ||
return new Response(JSON.stringify({ detail: '로그인 정보가 일치하지 않습니다.' }), { | ||
status: 401, | ||
statusText: '[Mock Data Error] Login Failed', | ||
}); | ||
} | ||
|
||
|
@@ -27,7 +26,6 @@ const authHandlers = [ | |
|
||
return new Response(responseBody, { | ||
status: 201, | ||
statusText: 'Created', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
|
Oops, something went wrong.