diff --git a/.pnp.cjs b/.pnp.cjs index c36c7aba..9fdb8a01 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -484,6 +484,7 @@ const RAW_RUNTIME_STATE = ["@types/react-dom", "npm:18.2.18"],\ ["react", "npm:18.2.0"],\ ["react-dom", "virtual:de80dc576383b2386358abc0e9fe49c00e3397fe355a0337462b73ab3115c2e557eb85784ee0fe776394cc11dd020b4e84dbbd75acf72ee6d54415d82d21f5c5#npm:18.2.0"],\ + ["react-hot-toast", "virtual:9ef42ff9c873460955cc48cd9b15127324f3d1f83a4bea8e6327df0101bb993bef095b175f8d10a3f0d23ee47f702ca3ef7272cba815f708e8609d03d84b96a2#npm:2.4.1"],\ ["typescript", "patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7"]\ ],\ "linkType": "SOFT"\ @@ -2901,6 +2902,28 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["goober", [\ + ["npm:2.1.14", {\ + "packageLocation": "../../.yarn/berry/cache/goober-npm-2.1.14-d5c148d4a9-10c0.zip/node_modules/goober/",\ + "packageDependencies": [\ + ["goober", "npm:2.1.14"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:56188b0021144eef48b2dd78da088e7acb06822989aab900ff8b5537ab8743edf7b0965e422a10883590077eb65fe0bae60a093dfe7ec2dd9ba3deed90ec3cab#npm:2.1.14", {\ + "packageLocation": "./.yarn/__virtual__/goober-virtual-5921a87690/3/.yarn/berry/cache/goober-npm-2.1.14-d5c148d4a9-10c0.zip/node_modules/goober/",\ + "packageDependencies": [\ + ["goober", "virtual:56188b0021144eef48b2dd78da088e7acb06822989aab900ff8b5537ab8743edf7b0965e422a10883590077eb65fe0bae60a093dfe7ec2dd9ba3deed90ec3cab#npm:2.1.14"],\ + ["@types/csstype", null],\ + ["csstype", null]\ + ],\ + "packagePeers": [\ + "@types/csstype",\ + "csstype"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["graceful-fs", [\ ["npm:4.2.11", {\ "packageLocation": "../../.yarn/berry/cache/graceful-fs-npm-4.2.11-24bb648a68-10c0.zip/node_modules/graceful-fs/",\ @@ -3854,6 +3877,33 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["react-hot-toast", [\ + ["npm:2.4.1", {\ + "packageLocation": "../../.yarn/berry/cache/react-hot-toast-npm-2.4.1-923d48e94b-10c0.zip/node_modules/react-hot-toast/",\ + "packageDependencies": [\ + ["react-hot-toast", "npm:2.4.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:9ef42ff9c873460955cc48cd9b15127324f3d1f83a4bea8e6327df0101bb993bef095b175f8d10a3f0d23ee47f702ca3ef7272cba815f708e8609d03d84b96a2#npm:2.4.1", {\ + "packageLocation": "./.yarn/__virtual__/react-hot-toast-virtual-56188b0021/3/.yarn/berry/cache/react-hot-toast-npm-2.4.1-923d48e94b-10c0.zip/node_modules/react-hot-toast/",\ + "packageDependencies": [\ + ["react-hot-toast", "virtual:9ef42ff9c873460955cc48cd9b15127324f3d1f83a4bea8e6327df0101bb993bef095b175f8d10a3f0d23ee47f702ca3ef7272cba815f708e8609d03d84b96a2#npm:2.4.1"],\ + ["@types/react", "npm:18.2.48"],\ + ["@types/react-dom", "npm:18.2.18"],\ + ["goober", "virtual:56188b0021144eef48b2dd78da088e7acb06822989aab900ff8b5537ab8743edf7b0965e422a10883590077eb65fe0bae60a093dfe7ec2dd9ba3deed90ec3cab#npm:2.1.14"],\ + ["react", "npm:18.2.0"],\ + ["react-dom", "virtual:de80dc576383b2386358abc0e9fe49c00e3397fe355a0337462b73ab3115c2e557eb85784ee0fe776394cc11dd020b4e84dbbd75acf72ee6d54415d82d21f5c5#npm:18.2.0"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["react-is", [\ ["npm:16.13.1", {\ "packageLocation": "../../.yarn/berry/cache/react-is-npm-16.13.1-a9b9382b4f-10c0.zip/node_modules/react-is/",\ diff --git a/apps/admin/src/pages/HomePage/HomePage.tsx b/apps/admin/src/pages/HomePage/HomePage.tsx index 50d4b43d..7be8e9e5 100644 --- a/apps/admin/src/pages/HomePage/HomePage.tsx +++ b/apps/admin/src/pages/HomePage/HomePage.tsx @@ -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 ( { } >

HomePage

+
+ +
+
+ +
+
+ +
+
+ +
); }; diff --git a/packages/ui/package.json b/packages/ui/package.json index dcd0fab6..abc22e70 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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": "*", diff --git a/packages/ui/src/components/ThemeProvider/index.tsx b/packages/ui/src/components/ThemeProvider/index.tsx index 824fa5cd..d8c8ad14 100644 --- a/packages/ui/src/components/ThemeProvider/index.tsx +++ b/packages/ui/src/components/ThemeProvider/index.tsx @@ -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, @@ -15,7 +17,10 @@ interface ThemeProviderProps { } const ThemeProvider = ({ children }: ThemeProviderProps) => ( - {children} + + {children} + + ); export default ThemeProvider; diff --git a/packages/ui/src/components/Toast/Toast.styles.ts b/packages/ui/src/components/Toast/Toast.styles.ts new file mode 100644 index 00000000..e3926eb7 --- /dev/null +++ b/packages/ui/src/components/Toast/Toast.styles.ts @@ -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, +}; diff --git a/packages/ui/src/components/Toast/icons.tsx b/packages/ui/src/components/Toast/icons.tsx new file mode 100644 index 00000000..4a6623df --- /dev/null +++ b/packages/ui/src/components/Toast/icons.tsx @@ -0,0 +1,52 @@ +export const SuccessIcon = () => ( + + + + +); + +export const WarningIcon = () => ( + + + + + +); + +export const ErrorIcon = () => ( + + + + + +); + +export const InfoIcon = () => ( + + + + +); diff --git a/packages/ui/src/components/Toast/index.tsx b/packages/ui/src/components/Toast/index.tsx new file mode 100644 index 00000000..9bc808ce --- /dev/null +++ b/packages/ui/src/components/Toast/index.tsx @@ -0,0 +1,25 @@ +import { ToastBar, Toaster } from 'react-hot-toast'; +import Styled from './Toast.styles'; + +const Toast = () => { + return ( + + {(toast) => ( + + {({ icon, message }) => ( + + {icon} + {message} + + )} + + )} + + ); +}; + +export default Toast; diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts new file mode 100644 index 00000000..bacab629 --- /dev/null +++ b/packages/ui/src/hooks/index.ts @@ -0,0 +1,3 @@ +import useToast from './useToast'; + +export { useToast }; diff --git a/packages/ui/src/hooks/useToast.tsx b/packages/ui/src/hooks/useToast.tsx new file mode 100644 index 00000000..a952fcfa --- /dev/null +++ b/packages/ui/src/hooks/useToast.tsx @@ -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; + +const DEFAULT_DURATION = 3000; + +const useToast = () => { + const theme = useTheme(); + + const toastVariables = useMemo( + () => + ({ + success: { + icon: , + 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: , + 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: , + 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: , + 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, [key, variables]) => ({ + ...acc, + [key]: (message: string, options?: ToastOptions) => { + const duration = options?.duration ?? DEFAULT_DURATION; + + return toast(message, { + ...variables, + duration, + }); + }, + }), + {}, + ); +}; + +export default useToast; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 0a5a463d..5e9c73f8 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,2 +1,3 @@ export * from './systems'; export * from './components'; +export * from './hooks'; diff --git a/yarn.lock b/yarn.lock index 7d3e43ed..ad38b53d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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 @@ -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" @@ -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"