Skip to content

Commit

Permalink
feat: 토스트 컴포넌트 구현 (#17)
Browse files Browse the repository at this point in the history
* feat: 토스트 컴포넌트 구현

* refactor: useToast에서 상수 객체로부터 toast 함수를 생성하는 구조로 변경
  • Loading branch information
Puterism authored Jan 27, 2024
1 parent ba22754 commit 0737dbd
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 2 deletions.
50 changes: 50 additions & 0 deletions .pnp.cjs

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

39 changes: 39 additions & 0 deletions apps/admin/src/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useToast } from '@boolti/ui';
import Header from '../../components/Header/Header';
import Layout from '../../components/Layout/Layout';
import { PATH } from '../../constants/routes';
import Styled from './HomePage.styles';

const HomePage = () => {
const toast = useToast();

return (
<Layout
header={
Expand All @@ -14,6 +17,42 @@ const HomePage = () => {
}
>
<h2>HomePage</h2>
<div>
<button
onClick={() => {
toast.success('성공했을 때 메세지입니다.');
}}
>
성공 토스트 띄우기
</button>
</div>
<div>
<button
onClick={() => {
toast.warning('경고 메세지입니다.');
}}
>
경고 토스트 띄우기
</button>
</div>
<div>
<button
onClick={() => {
toast.error('에러 메세지입니다.');
}}
>
에러 토스트 띄우기
</button>
</div>
<div>
<button
onClick={() => {
toast.info('정보제공 메세지입니다.');
}}
>
정보 제공 토스트 띄우기
</button>
</div>
</Layout>
);
};
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1"
},
"devDependencies": {
"@boolti/eslint-config": "*",
Expand Down
7 changes: 6 additions & 1 deletion packages/ui/src/components/ThemeProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import palette from '../../systems/palette';
import typo from '../../systems/typo';
import breakpoint from '../../systems/breakpoint';

import Toast from '../Toast';

const theme = {
palette,
typo,
Expand All @@ -15,7 +17,10 @@ interface ThemeProviderProps {
}

const ThemeProvider = ({ children }: ThemeProviderProps) => (
<BaseThemeProvider theme={theme}>{children}</BaseThemeProvider>
<BaseThemeProvider theme={theme}>
{children}
<Toast />
</BaseThemeProvider>
);

export default ThemeProvider;
28 changes: 28 additions & 0 deletions packages/ui/src/components/Toast/Toast.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styled from '@emotion/styled';

const Toast = styled.div`
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 16px;
`;

const Icon = styled.div`
display: inline-flex;
align-items: center;
`;

const Message = styled.div`
${({ theme }) => theme.typo.b1};
div {
margin: 0;
}
`;

export default {
Toast,
Icon,
Message,
};
52 changes: 52 additions & 0 deletions packages/ui/src/components/Toast/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export const SuccessIcon = () => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="10" fill="#52C41A" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.2098 6.07913C15.5343 6.37898 15.5543 6.88511 15.2544 7.20962L9.09441 13.8763C8.93219 14.0518 8.69997 14.1457 8.46133 14.1321C8.22268 14.1185 8.00262 13.9989 7.8614 13.806L4.68806 9.4727C4.42702 9.11623 4.50437 8.61564 4.86084 8.3546C5.21731 8.09355 5.7179 8.17091 5.97894 8.52738L8.57842 12.0771L14.0793 6.12379C14.3791 5.79928 14.8852 5.77929 15.2098 6.07913Z"
fill="white"
/>
</svg>
);

export const WarningIcon = () => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="10" fill="#FAAD14" />
<circle cx="9.99874" cy="15" r="0.833333" fill="white" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.0002 4.66663C10.4604 4.66663 10.8335 5.00708 10.8335 5.42704V12.0173C10.8335 12.4373 10.4604 12.7777 10.0002 12.7777C9.53993 12.7777 9.16683 12.4373 9.16683 12.0173V5.42704C9.16683 5.00708 9.53993 4.66663 10.0002 4.66663Z"
fill="white"
/>
</svg>
);

export const ErrorIcon = () => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="10" fill="#FF4D4F" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.1501 14.3369C13.8377 14.6493 13.3312 14.6493 13.0188 14.3369L5.66486 6.98296C5.35244 6.67054 5.35244 6.164 5.66486 5.85159C5.97728 5.53917 6.48381 5.53917 6.79623 5.85159L14.1501 13.2055C14.4626 13.5179 14.4626 14.0244 14.1501 14.3369Z"
fill="white"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5.66486 14.1483C5.35244 13.8359 5.35244 13.3294 5.66486 13.0169L13.0188 5.66303C13.3312 5.35061 13.8377 5.35061 14.1501 5.66303C14.4626 5.97545 14.4626 6.48198 14.1501 6.7944L6.79623 14.1483C6.48381 14.4607 5.97728 14.4607 5.66486 14.1483Z"
fill="white"
/>
</svg>
);

export const InfoIcon = () => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="10" fill="#1890FF" />
<path
d="M7.86841 14.79L8.70841 14.58V8.41995L7.86841 8.20995V7.99995H9.68841C10.5284 7.99995 10.9484 8.41995 10.9484 9.25995V14.58L11.7884 14.79V15H7.86841V14.79ZM9.68841 7.15995C9.40841 7.15995 9.17507 7.06662 8.98841 6.87995C8.80174 6.69328 8.70841 6.45995 8.70841 6.17995C8.70841 5.89995 8.80174 5.66662 8.98841 5.47995C9.17507 5.29328 9.40841 5.19995 9.68841 5.19995C9.96841 5.19995 10.2017 5.29328 10.3884 5.47995C10.5751 5.66662 10.6684 5.89995 10.6684 6.17995C10.6684 6.45995 10.5751 6.69328 10.3884 6.87995C10.2017 7.06662 9.96841 7.15995 9.68841 7.15995Z"
fill="white"
/>
</svg>
);
25 changes: 25 additions & 0 deletions packages/ui/src/components/Toast/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ToastBar, Toaster } from 'react-hot-toast';
import Styled from './Toast.styles';

const Toast = () => {
return (
<Toaster
containerStyle={{
top: 100,
}}
>
{(toast) => (
<ToastBar toast={toast} style={{ padding: '0' }}>
{({ icon, message }) => (
<Styled.Toast>
<Styled.Icon>{icon}</Styled.Icon>
<Styled.Message>{message}</Styled.Message>
</Styled.Toast>
)}
</ToastBar>
)}
</Toaster>
);
};

export default Toast;
3 changes: 3 additions & 0 deletions packages/ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import useToast from './useToast';

export { useToast };
76 changes: 76 additions & 0 deletions packages/ui/src/hooks/useToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useTheme } from '@emotion/react';
import { useMemo } from 'react';
import { ErrorIcon, toast } from 'react-hot-toast';
import { InfoIcon, SuccessIcon, WarningIcon } from '../components/Toast/icons';

interface ToastOptions {
duration?: number;
}

type ToastFunction = (message: string, options?: ToastOptions) => ReturnType<typeof toast>;

const DEFAULT_DURATION = 3000;

const useToast = () => {
const theme = useTheme();

const toastVariables = useMemo(
() =>
({
success: {
icon: <SuccessIcon />,
style: {
border: '1px solid #C3ECC2',
backgroundColor: 'rgba(239, 250, 239, 0.95)',
boxShadow: '0px 4px 15px 0px rgba(0, 0, 0, 0.15)',
color: theme.palette.grey.g90,
},
},
warning: {
icon: <WarningIcon />,
style: {
border: '1px solid #FCEACE',
backgroundColor: 'rgba(254, 250, 243, 0.95)',
boxShadow: '0px 4px 15px 0px rgba(0, 0, 0, 0.15)',
color: theme.palette.grey.g90,
},
},
error: {
icon: <ErrorIcon />,
style: {
border: '1px solid #FACBCF',
backgroundColor: 'rgba(254, 248, 248, 0.95)',
boxShadow: '0px 4px 15px 0px rgba(0, 0, 0, 0.15)',
color: theme.palette.grey.g90,
},
},
info: {
icon: <InfoIcon />,
style: {
border: '1px solid #C6E0FF',
background: 'rgba(239, 245, 255, 0.95)',
boxShadow: '0px 4px 15px 0px rgba(0, 0, 0, 0.15)',
color: theme.palette.grey.g90,
},
},
}) as const,
[theme.palette.grey.g90],
);

return Object.entries(toastVariables).reduce(
(acc: Record<string, ToastFunction>, [key, variables]) => ({
...acc,
[key]: (message: string, options?: ToastOptions) => {
const duration = options?.duration ?? DEFAULT_DURATION;

return toast(message, {
...variables,
duration,
});
},
}),
{},
);
};

export default useToast;
1 change: 1 addition & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './systems';
export * from './components';
export * from './hooks';
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ __metadata:
"@types/react-dom": "npm:^18.2.17"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
react-hot-toast: "npm:^2.4.1"
typescript: "npm:^5.2.2"
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -2223,6 +2224,15 @@ __metadata:
languageName: node
linkType: hard

"goober@npm:^2.1.10":
version: 2.1.14
resolution: "goober@npm:2.1.14"
peerDependencies:
csstype: ^3.0.10
checksum: 184eda787a9a14cffbaa8284e98dc127095e538b4acab2a84b81babca84253bb883e16208822e02584f27c7a69f3ec47341e5060dfa40a0e07c32ac1f79b2714
languageName: node
linkType: hard

"graceful-fs@npm:^4.2.6":
version: 4.2.11
resolution: "graceful-fs@npm:4.2.11"
Expand Down Expand Up @@ -3047,6 +3057,18 @@ __metadata:
languageName: node
linkType: hard

"react-hot-toast@npm:^2.4.1":
version: 2.4.1
resolution: "react-hot-toast@npm:2.4.1"
dependencies:
goober: "npm:^2.1.10"
peerDependencies:
react: ">=16"
react-dom: ">=16"
checksum: 591ecec3c6adc1cdb70f00165a57baa3d7f75d0d30fa767213c36496bdcc6be2b2e6a3edbf7c04f7d726a1b17dcfb5e7feb2136b04b17c9ccb769894b970f365
languageName: node
linkType: hard

"react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
Expand Down

0 comments on commit 0737dbd

Please sign in to comment.