Skip to content

Commit 7e90b95

Browse files
authored
Create Notification Component (#5955)
feat: create Notification component
1 parent 6109d75 commit 7e90b95

File tree

9 files changed

+217
-3
lines changed

9 files changed

+217
-3
lines changed

.storybook/preview.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import classNames from 'classnames';
33
import { withThemeByDataAttribute } from '@storybook/addon-themes';
44
import { SiteProvider } from '../providers/siteProvider';
55
import { LocaleProvider } from '../providers/localeProvider';
6+
import { NotificationProvider } from '../providers/notificationProvider';
67
import * as constants from './constants';
78
import type { Preview, ReactRenderer } from '@storybook/react';
89

@@ -30,9 +31,11 @@ const preview: Preview = {
3031
Story => (
3132
<SiteProvider>
3233
<LocaleProvider>
33-
<div className={rootClasses}>
34-
<Story />
35-
</div>
34+
<NotificationProvider viewportClassName="absolute top-0 left-0 list-none">
35+
<div className={rootClasses}>
36+
<Story />
37+
</div>
38+
</NotificationProvider>
3639
</LocaleProvider>
3740
</SiteProvider>
3841
),
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.root {
2+
@apply m-6
3+
rounded
4+
border
5+
border-neutral-200
6+
bg-white
7+
px-4
8+
py-3
9+
shadow-lg
10+
dark:border-neutral-800
11+
dark:bg-neutral-900;
12+
}
13+
14+
.message {
15+
@apply font-medium
16+
text-green-600
17+
dark:text-white;
18+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { CodeBracketIcon } from '@heroicons/react/24/solid';
2+
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
3+
import { FormattedMessage } from 'react-intl';
4+
5+
import Notification from './index';
6+
7+
type Story = StoryObj<typeof Notification>;
8+
type Meta = MetaObj<typeof Notification>;
9+
10+
export const Default: Story = {
11+
args: {
12+
open: true,
13+
duration: 5000,
14+
children: 'Copied to clipboard!',
15+
},
16+
};
17+
18+
export const TimedNotification: Story = {
19+
args: {
20+
duration: 5000,
21+
children: 'Copied to clipboard!',
22+
},
23+
};
24+
25+
export const WithJSX: Story = {
26+
args: {
27+
open: true,
28+
children: (
29+
<div className="flex items-center gap-3">
30+
<CodeBracketIcon className="h-4 w-4" />
31+
<FormattedMessage id="components.common.codebox.copied" />
32+
</div>
33+
),
34+
},
35+
};
36+
37+
export default { component: Notification } as Meta;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as ToastPrimitive from '@radix-ui/react-toast';
2+
import classNames from 'classnames';
3+
import type { FC } from 'react';
4+
5+
import styles from './index.module.css';
6+
7+
type NotificationProps = {
8+
open?: boolean;
9+
duration?: number;
10+
onChange?: (value: boolean) => void;
11+
children?: React.ReactNode;
12+
className?: string;
13+
};
14+
15+
const Notification: FC<NotificationProps> = ({
16+
open,
17+
duration = 5000,
18+
onChange,
19+
children,
20+
className,
21+
}: NotificationProps) => (
22+
<ToastPrimitive.Root
23+
open={open}
24+
duration={duration}
25+
onOpenChange={onChange}
26+
className={classNames(styles.root, className)}
27+
>
28+
<ToastPrimitive.Title className={styles.message}>
29+
{children}
30+
</ToastPrimitive.Title>
31+
</ToastPrimitive.Root>
32+
);
33+
34+
export default Notification;

hooks/useNotification.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { useContext } from 'react';
2+
3+
import { NotificationDispatch } from '@/providers/notificationProvider';
4+
5+
export const useNotification = () => useContext(NotificationDispatch);

i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"components.pagination.previous": "Older",
3737
"components.common.crossLink.previous": "Prev",
3838
"components.common.crossLink.next": "Next",
39+
"components.common.codebox.copied": "Copied to clipboard!",
3940
"layouts.blogPost.author.byLine": "{author, select, null {} other {By {author}, }}",
4041
"layouts.blogIndex.currentYear": "News from {year}",
4142
"components.api.jsonLink.title": "View as JSON",

package-lock.json

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@mdx-js/react": "^2.3.0",
4545
"@nodevu/core": "~0.1.0",
4646
"@radix-ui/react-select": "^2.0.0",
47+
"@radix-ui/react-toast": "^1.1.5",
4748
"@types/node": "18.18.3",
4849
"@vcarl/remark-headings": "~0.1.0",
4950
"@vercel/analytics": "^1.0.2",

providers/notificationProvider.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as Toast from '@radix-ui/react-toast';
2+
import type {
3+
Dispatch,
4+
FC,
5+
PropsWithChildren,
6+
ReactNode,
7+
SetStateAction,
8+
} from 'react';
9+
import { createContext, useEffect, useState } from 'react';
10+
11+
import Notification from '@/components/Common/Notification';
12+
13+
type NotificationContextType = {
14+
message: string | ReactNode;
15+
duration: number;
16+
} | null;
17+
18+
type NotificationProps = {
19+
viewportClassName?: string;
20+
};
21+
22+
const NotificationContext = createContext<NotificationContextType>(null);
23+
24+
export const NotificationDispatch = createContext<
25+
Dispatch<SetStateAction<NotificationContextType>>
26+
>(() => {});
27+
28+
export const NotificationProvider: FC<PropsWithChildren<NotificationProps>> = ({
29+
viewportClassName,
30+
children,
31+
}) => {
32+
const [notification, dispatch] = useState<NotificationContextType>(null);
33+
34+
useEffect(() => {
35+
const timeout = setTimeout(() => dispatch(null), notification?.duration);
36+
37+
return () => clearTimeout(timeout);
38+
}, [notification]);
39+
40+
return (
41+
<NotificationContext.Provider value={notification}>
42+
<NotificationDispatch.Provider value={dispatch}>
43+
<Toast.Provider>
44+
{children}
45+
46+
<Toast.Viewport className={viewportClassName} />
47+
{notification && (
48+
<Notification duration={notification.duration}>
49+
{notification.message}
50+
</Notification>
51+
)}
52+
</Toast.Provider>
53+
</NotificationDispatch.Provider>
54+
</NotificationContext.Provider>
55+
);
56+
};

0 commit comments

Comments
 (0)