-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: @radix-ui/react-portal 패키지 설치 * feat: Toast 컴포넌트 추가 * chore: position을 고정 * feat: ToastOptionAtom으로 position 관리할 수 있도록 * feat: 부가적인 옵션만을 받아 default + 부가옵션을 설정할 수 있도록 * refactor: sequance 대신 length로 id 부여
- Loading branch information
1 parent
57b1a17
commit bdf4350
Showing
11 changed files
with
267 additions
and
34 deletions.
There are no files selected for viewing
Binary file not shown.
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,41 @@ | ||
import { atom } from 'jotai'; | ||
|
||
import type { ToastProps } from './Toast'; | ||
|
||
type ToastsProps = ToastProps[]; | ||
|
||
export interface ToastOptionProps { | ||
position?: string; | ||
} | ||
|
||
export const toastsAtom = atom<ToastsProps>([]); | ||
|
||
export const toastOptionAtom = atom<ToastOptionProps>({ | ||
position: 'bottom-[84px]', | ||
}); | ||
|
||
export const removeToastAtom = atom(null, (get, set, id: number) => { | ||
const prev = get(toastsAtom); | ||
set( | ||
toastsAtom, | ||
prev.filter((toast) => toast.id !== id), | ||
); | ||
}); | ||
|
||
export const toastAtom = atom( | ||
(get) => get(toastsAtom), | ||
(get, set, type: ToastProps['type']) => (title: string) => () => { | ||
const prev = get(toastsAtom); | ||
const newToast = { type, title, id: prev.length }; | ||
set(toastsAtom, [...prev, newToast]); | ||
}, | ||
); | ||
|
||
export const toastOptionChangeAtom = atom( | ||
(get) => get(toastOptionAtom), | ||
(get, set, changeOption: ToastOptionProps) => { | ||
const prev = get(toastOptionAtom); | ||
const updatedOption = { ...prev, ...changeOption }; | ||
set(toastOptionAtom, updatedOption); | ||
}, | ||
); |
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,38 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import { useToast } from '@/hooks/useToast'; | ||
|
||
import { Button } from '..'; | ||
|
||
import { Toast } from './Toast'; | ||
import { ToastProvider } from './ToastProvider'; | ||
|
||
const meta: Meta<typeof Toast> = { | ||
title: 'components/atoms/toast', | ||
component: Toast, | ||
decorators: [ | ||
(Story) => ( | ||
<div className="w-[300px] h-[100vh]"> | ||
<ToastProvider /> | ||
<Story /> | ||
</div> | ||
), | ||
], | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof Toast>; | ||
|
||
export const Basic: Story = { | ||
args: { | ||
title: '세부 목표를 삭제했어요.', | ||
type: 'success', | ||
}, | ||
}; | ||
|
||
export const ClickToToast = () => { | ||
const toast = useToast(); | ||
|
||
return <Button onClick={toast.success('세부 목표를 삭제했어요.')}>누르면 토스트가 나와요</Button>; | ||
}; |
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,57 @@ | ||
'use client'; | ||
|
||
import { useEffect, useState } from 'react'; | ||
import { useSetAtom } from 'jotai'; | ||
|
||
import SuccessIcon from '@/assets/icons/toast-success.svg'; | ||
import WarningIcon from '@/assets/icons/toast-warning.svg'; | ||
|
||
import { Typography } from '../typography'; | ||
|
||
import { removeToastAtom } from './Toast.atom'; | ||
|
||
export interface ToastProps { | ||
id: number; | ||
title: string; | ||
type?: 'success' | 'warning'; | ||
} | ||
|
||
const toastIcon = { | ||
success: <SuccessIcon />, | ||
warning: <WarningIcon />, | ||
}; | ||
|
||
const TOAST_DURATION = 3000; | ||
const ANIMATION_DURATION = 350; | ||
|
||
export const Toast = ({ id, title, type = 'success' }: ToastProps) => { | ||
const [opacity, setOpacity] = useState('opacity-[0.2]'); | ||
const removeToastItem = useSetAtom(removeToastAtom); | ||
|
||
useEffect(() => { | ||
setOpacity('opacity-[0.8]'); | ||
const timeoutForRemove = setTimeout(() => { | ||
removeToastItem(id); | ||
}, TOAST_DURATION); | ||
|
||
const timeoutForVisible = setTimeout(() => { | ||
setOpacity('opacity-0'); | ||
}, TOAST_DURATION - ANIMATION_DURATION); | ||
|
||
return () => { | ||
clearTimeout(timeoutForRemove); | ||
clearTimeout(timeoutForVisible); | ||
}; | ||
}, [id, removeToastItem]); | ||
|
||
return ( | ||
<div | ||
className={`w-fit flex gap-5xs justify-center items-center px-3xs py-5xs bg-gray-60 rounded-[12px] mb-5xs transition-all duration-350 ease-in-out ${opacity}`} | ||
> | ||
{toastIcon[type]} | ||
<Typography type="body3" className="text-white"> | ||
{title} | ||
</Typography> | ||
</div> | ||
); | ||
}; |
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,20 @@ | ||
import * as Portal from '@radix-ui/react-portal'; | ||
import { useAtomValue } from 'jotai'; | ||
|
||
import { Toast } from './Toast'; | ||
import { toastOptionAtom, toastsAtom } from './Toast.atom'; | ||
|
||
export const ToastProvider = () => { | ||
const toasts = useAtomValue(toastsAtom); | ||
const { position } = useAtomValue(toastOptionAtom); | ||
|
||
return ( | ||
<Portal.Root> | ||
<div className={`fixed ${position} left-1/2 transform translate-x-[-50%]`}> | ||
{toasts.map((toast) => ( | ||
<Toast key={toast.id} {...toast} /> | ||
))} | ||
</div> | ||
</Portal.Root> | ||
); | ||
}; |
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,19 @@ | ||
import { useEffect } from 'react'; | ||
import { useSetAtom } from 'jotai'; | ||
|
||
import type { ToastOptionProps } from '@/components/atoms/toast/Toast.atom'; | ||
import { toastAtom, toastOptionChangeAtom } from '@/components/atoms/toast/Toast.atom'; | ||
|
||
export const useToast = (option?: ToastOptionProps) => { | ||
const addToast = useSetAtom(toastAtom); | ||
const setOption = useSetAtom(toastOptionChangeAtom); | ||
|
||
useEffect(() => { | ||
if (option) setOption(option); | ||
}, [option, setOption]); | ||
|
||
return { | ||
success: addToast('success'), | ||
warning: addToast('warning'), | ||
}; | ||
}; |
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