Skip to content

Commit

Permalink
Toast 구현 with Jotai (#166)
Browse files Browse the repository at this point in the history
* feat: toast 구현

* chore: 스토리북 버튼 수정
  • Loading branch information
sjoleee authored May 30, 2023
1 parent 0bad1fa commit c50af38
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { pretendard } from '../src/assets/fonts/pretendard';
import defaultTheme from '../src/styles/theme';
import { domMax, LazyMotion } from 'framer-motion';
import GlobalStyle from '../src/styles/GlobalStyle';
import ToastWrapper from '../src/components/toast/ToastWrapper';

const previewDecorator = (Story) => {
useEffect(() => {
Expand All @@ -17,6 +18,7 @@ const previewDecorator = (Story) => {
<LazyMotion features={domMax}>
<div className={pretendard.className} style={{ width: '100%', maxWidth: '480px', margin: '0 auto' }}>
<Story />
<ToastWrapper />
</div>
</LazyMotion>
</ThemeProvider>
Expand Down
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"axios": "^1.3.5",
"dotenv": "^16.0.3",
"framer-motion": "^10.12.2",
"jotai": "^2.1.0",
"mixpanel-browser": "^2.46.0",
"next": "13.3.0",
"next-auth": "^4.22.1",
Expand Down
38 changes: 38 additions & 0 deletions src/components/toast/Toast.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { type Meta } from '@storybook/react';

import Button from '../button/Button';
import WarningIcon from '../icons/WarningIcon';
import Toast from './Toast';
import useToast from './useToast';

const meta: Meta<typeof Toast.Content> = {
title: 'Toast',
component: Toast.Content,
};

export default meta;

export function Default() {
const { fireToast } = useToast();

return (
<div>
<Button onClick={() => fireToast({ content: '토스트 메세지 입니다' })}>토스트 발사</Button>
<Button
onClick={() =>
fireToast({
content: (
<>
<WarningIcon />
<Toast.Text>토스트 메시지 with 아이콘</Toast.Text>
</>
),
higherThanCTA: true,
})
}
>
다른 토스트 발사
</Button>
</div>
);
}
39 changes: 39 additions & 0 deletions src/components/toast/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { css, type Theme } from '@emotion/react';

import { type ToastProps } from '~/store/toast';
import { BODY_1 } from '~/styles/typo';

const Content = ({ content }: Pick<ToastProps, 'content'>) => {
return <div css={toastCss}>{typeof content === 'string' ? <Toast.Text>{content}</Toast.Text> : content}</div>;
};

const toastCss = css`
display: flex;
gap: 8px;
align-items: center;
justify-content: center;
height: 56px;
padding: 16px 20px;
background-color: #394258cc;
backdrop-filter: blur(4px);
border-radius: 44px;
`;

const Text = ({ children }: { children: string }) => {
return <span css={toastTextCss}>{children}</span>;
};

const toastTextCss = ({ colors }: Theme) => css`
${BODY_1};
color: ${colors.white};
`;

const Toast = {
Content,
Text,
};

export default Toast;
49 changes: 49 additions & 0 deletions src/components/toast/ToastWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { css } from '@emotion/react';
import { AnimatePresence, m } from 'framer-motion';
import { useAtomValue } from 'jotai';

import { defaultFadeInUpVariants } from '~/constants/motions';
import { toastAtom } from '~/store/toast';

import Toast from './Toast';

const CTA_HEIGHT = 92;
const CTA_PADDING = 8;
const BOTTOM_SAFETY_HEIGHT = 24;

const ToastWrapper = () => {
const toast = useAtomValue(toastAtom);

return (
<div>
<AnimatePresence mode="wait">
{toast && (
<m.div
key={String(toast.content)}
css={toastContainerCss(toast.higherThanCTA ?? false)}
variants={defaultFadeInUpVariants}
initial="initial"
animate="animate"
exit="exit"
>
<Toast.Content content={toast.content} />
</m.div>
)}
</AnimatePresence>
</div>
);
};

export default ToastWrapper;

const toastContainerCss = (higherThanCTA: boolean) => css`
position: fixed;
bottom: ${higherThanCTA ? CTA_HEIGHT + CTA_PADDING : BOTTOM_SAFETY_HEIGHT}px;
left: 0;
display: flex;
justify-content: center;
width: 100%;
height: fit-content;
`;
35 changes: 35 additions & 0 deletions src/components/toast/useToast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type ReactElement, useCallback } from 'react';
import { useAtom } from 'jotai';

import { toastAtom } from '~/store/toast';

interface FireToast {
content: string | ReactElement;
duration?: number;
higherThanCTA?: boolean;
}

const DEFAULT_DURATION = 1500;

const useToast = () => {
const [toast, setToast] = useAtom(toastAtom);

const removeToast = useCallback((id: string) => {
setToast((prev) => {
if (!prev) return null;
if (prev.id === id) return null;

return prev;
});
}, []);

const fireToast = useCallback(({ content, duration = DEFAULT_DURATION, higherThanCTA }: FireToast) => {
const id = new Date().getTime().toString();
setToast({ id, content, higherThanCTA });
setTimeout(() => removeToast(id), duration);
}, []);

return { toast, fireToast };
};

export default useToast;
2 changes: 2 additions & 0 deletions src/pages/_app.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { domMax, LazyMotion } from 'framer-motion';

import { pretendard } from '~/assets/fonts/pretendard';
import MonitoringInitializer from '~/components/monitoring/MonitoringInitializer';
import ToastWrapper from '~/components/toast/ToastWrapper';
import GlobalStyles from '~/styles/GlobalStyle';
import defaultTheme from '~/styles/theme';

Expand Down Expand Up @@ -45,6 +46,7 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) {
<GlobalStyles />
<div css={defaultLayoutCss} className={pretendard.className}>
{getLayout(<Component {...pageProps} />)}
<ToastWrapper />
</div>
</LazyMotion>
</ThemeProvider>
Expand Down
10 changes: 10 additions & 0 deletions src/store/toast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { type ReactElement } from 'react';
import { atom } from 'jotai';

export interface ToastProps {
id: string;
content: string | ReactElement;
higherThanCTA?: boolean;
}

export const toastAtom = atom<ToastProps | null>(null);
13 changes: 13 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ __metadata:
eslint-plugin-unused-imports: ^2.0.0
framer-motion: ^10.12.2
husky: ^8.0.3
jotai: ^2.1.0
jsdom: ^21.1.1
lint-staged: ^13.2.1
mixpanel-browser: ^2.46.0
Expand Down Expand Up @@ -11106,6 +11107,18 @@ __metadata:
languageName: node
linkType: hard

"jotai@npm:^2.1.0":
version: 2.1.0
resolution: "jotai@npm:2.1.0"
peerDependencies:
react: ">=17.0.0"
peerDependenciesMeta:
react:
optional: true
checksum: 060b78dd221950c52e2c796f122b86b2cbbe99ba2b57e8c999a9b5403fd5f970cb8834296f0ac20bace85af8f96a8aac573efb9515382d215bbeace6c417a074
languageName: node
linkType: hard

"js-sdsl@npm:^4.1.4":
version: 4.4.0
resolution: "js-sdsl@npm:4.4.0"
Expand Down

0 comments on commit c50af38

Please sign in to comment.