feat: rewrite message component

This commit is contained in:
qishaoxuan 2022-07-28 23:42:05 +08:00
parent d5f6ab0bb5
commit 0cce817813
10 changed files with 224 additions and 88 deletions

View File

@ -6,9 +6,7 @@ import { copy } from './copy';
const IconBooth: FC<{ name: string; Icon: FC<any> }> = ({ name, Icon }) => {
const on_click = () => {
copy(`<${name} />`);
message.success({
content: 'Copied.',
});
message.success('Copied ~');
};
return (
<IconContainer title={name} onClick={on_click}>

View File

@ -14,6 +14,7 @@
"@mui/x-date-pickers-pro": "^5.0.0-alpha.7",
"@types/react-date-range": "^1.4.4",
"clsx": "^1.2.0",
"notistack": "^2.0.5",
"react-date-range": "^1.4.0"
}
}

View File

@ -0,0 +1,83 @@
import {
NotificationInstance,
type NotificationController,
type NotificationInstanceProps,
type NotificationContent,
type NotificationOption,
type NotificationKey,
} from '../notification';
import {
SuccessMessage,
ErrorMessage,
InfoMessage,
WarningMessage,
} from './MessageContent';
type MessageMethod = (
message: NotificationContent,
option?: Omit<NotificationOption, 'content'>
) => Promise<NotificationKey>;
export class Message {
private _notificationController: NotificationController = null;
private _notificationProps: NotificationInstanceProps = {};
constructor(props: NotificationInstanceProps) {
this._notificationProps = props;
}
private _ensureController() {
if (!this._notificationController) {
NotificationInstance(
this._notificationProps,
notificationController => {
this._notificationController = notificationController;
}
);
}
}
public success: MessageMethod = async (message, option) => {
await this._ensureController();
return this._notificationController.add(message, {
content: (key, message) => (
<SuccessMessage id={key} message={message} />
),
...option,
});
};
public error: MessageMethod = async (message, option) => {
await this._ensureController();
return this._notificationController.add(message, {
content: (key, message) => (
<ErrorMessage id={key} message={message} />
),
...option,
});
};
public warning: MessageMethod = async (message, option) => {
await this._ensureController();
return this._notificationController.add(message, {
content: (key, message) => (
<WarningMessage id={key} message={message} />
),
...option,
});
};
public info: MessageMethod = async (message, option) => {
await this._ensureController();
return this._notificationController.add(message, {
content: (key, message) => (
<InfoMessage id={key} message={message} />
),
...option,
});
};
}

View File

@ -0,0 +1,69 @@
import { forwardRef } from 'react';
import { NotificationContentProps } from '../notification';
/* eslint-disable no-restricted-imports */
import Alert from '@mui/material/Alert';
// TODO: Variants types of message content await designers
const commonStyle = { background: '#fff' };
export const SuccessMessage = forwardRef<
HTMLDivElement,
NotificationContentProps
>(({ message, id }, ref) => {
return (
<Alert
variant="outlined"
severity="success"
ref={ref}
style={commonStyle}
>
{message}
</Alert>
);
});
export const ErrorMessage = forwardRef<
HTMLDivElement,
NotificationContentProps
>(({ message, id }, ref) => {
return (
<Alert
variant="outlined"
severity="error"
ref={ref}
style={commonStyle}
>
{message}
</Alert>
);
});
export const WarningMessage = forwardRef<
HTMLDivElement,
NotificationContentProps
>(({ message, id }, ref) => {
return (
<Alert
variant="outlined"
severity="warning"
ref={ref}
style={commonStyle}
>
{message}
</Alert>
);
});
export const InfoMessage = forwardRef<HTMLDivElement, NotificationContentProps>(
({ message, id }, ref) => {
return (
<Alert
variant="outlined"
severity="info"
ref={ref}
style={commonStyle}
>
{message}
</Alert>
);
}
);

View File

@ -1,48 +0,0 @@
import type { FC, ReactNode } from 'react';
import { createRoot } from 'react-dom/client';
import { styled } from '../styled';
import type { ContainerProps } from './types';
interface ShowProps {
Container: FC<ContainerProps>;
/**
* 0 2000
*/
duration?: number;
content: ReactNode;
}
export const show = ({ Container, duration = 2000, content }: ShowProps) => {
const root_element = document.createElement('div');
document.body.appendChild(root_element);
function close() {
document.body.removeChild(root_element);
}
const react_root = createRoot(root_element);
react_root.render(
<PortalContainer>
<Container content={content} duration={duration} close={close} />
</PortalContainer>
);
if (duration > 0) {
setTimeout(() => {
close();
}, duration);
}
return close;
};
const PortalContainer = styled('div')({
position: 'fixed',
top: '100px',
left: '50%',
transform: 'translateX(-50%)',
backgroundColor: '#fff',
zIndex: 100,
});

View File

@ -1,18 +1,9 @@
import type { ReactNode } from 'react';
import { Message } from './Message';
import { show } from './base';
import { Success } from './success';
interface SuccessProps {
/**
* 2000
*/
duration?: number;
content: ReactNode;
}
export const message = {
success({ duration, content }: SuccessProps) {
return show({ Container: Success, duration, content });
export const message = new Message({
anchorOrigin: {
vertical: 'top',
horizontal: 'center',
},
};
autoHideDuration: 2000,
});

View File

@ -1,14 +0,0 @@
import type { FC } from 'react';
import { styled } from '../styled';
import type { ContainerProps } from './types';
export const Success: FC<ContainerProps> = ({ content }) => {
return <Container>{content}</Container>;
};
const Container = styled('div')({
maxWidth: '200px',
backgroundColor: 'rgba(64, 223, 155)',
borderRadius: '4px',
padding: '8px',
});

View File

@ -1,7 +0,0 @@
import type { ReactNode } from 'react';
export interface ContainerProps {
content: ReactNode;
duration: number;
close: () => void;
}

View File

@ -0,0 +1,62 @@
import React, { forwardRef, useImperativeHandle } from 'react';
import {
SnackbarProvider,
useSnackbar,
type SnackbarProviderProps,
type OptionsObject,
type SnackbarMessage,
type SnackbarKey,
} from 'notistack';
import { createRoot } from 'react-dom/client';
export type NotificationController = {
add: (message: SnackbarMessage, options?: OptionsObject) => SnackbarKey;
close: (key?: SnackbarKey) => void;
};
export type NotificationInstanceProps = Omit<SnackbarProviderProps, 'children'>;
export type NotificationContentProps = {
id: SnackbarKey;
message: SnackbarMessage;
};
export type NotificationContent = SnackbarMessage;
export type NotificationOption = OptionsObject;
export type NotificationKey = SnackbarKey;
const Notification = forwardRef<NotificationController>((props, ref) => {
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
useImperativeHandle(ref, () => ({
add: (message, options) => {
return enqueueSnackbar(message, options);
},
close: key => closeSnackbar(key),
}));
return null;
});
export const NotificationInstance = (
snackbarProviderProps: NotificationInstanceProps,
callback: (notificationController: NotificationController) => void
) => {
const rootElement = document.createElement('div');
document.body.appendChild(rootElement);
const reactRoot = createRoot(rootElement);
// ReactDOM.unmountComponentAtNode will call the ref function again after execution, called to prevent repeated calls
let called = false;
// react mounted dom is async
const ref = async (notificationController: NotificationController) => {
if (called) {
return;
}
called = true;
callback(notificationController);
};
reactRoot.render(
<SnackbarProvider {...snackbarProviderProps}>
<Notification ref={ref} />
</SnackbarProvider>
);
};

View File

@ -0,0 +1 @@
export * from './Notification';