mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
feat: rewrite message component
This commit is contained in:
@@ -6,9 +6,7 @@ import { copy } from './copy';
|
|||||||
const IconBooth: FC<{ name: string; Icon: FC<any> }> = ({ name, Icon }) => {
|
const IconBooth: FC<{ name: string; Icon: FC<any> }> = ({ name, Icon }) => {
|
||||||
const on_click = () => {
|
const on_click = () => {
|
||||||
copy(`<${name} />`);
|
copy(`<${name} />`);
|
||||||
message.success({
|
message.success('Copied ~');
|
||||||
content: 'Copied.',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<IconContainer title={name} onClick={on_click}>
|
<IconContainer title={name} onClick={on_click}>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@mui/x-date-pickers-pro": "^5.0.0-alpha.7",
|
"@mui/x-date-pickers-pro": "^5.0.0-alpha.7",
|
||||||
"@types/react-date-range": "^1.4.4",
|
"@types/react-date-range": "^1.4.4",
|
||||||
"clsx": "^1.2.0",
|
"clsx": "^1.2.0",
|
||||||
|
"notistack": "^2.0.5",
|
||||||
"react-date-range": "^1.4.0"
|
"react-date-range": "^1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
libs/components/ui/src/message/Message.tsx
Normal file
83
libs/components/ui/src/message/Message.tsx
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
69
libs/components/ui/src/message/MessageContent.tsx
Normal file
69
libs/components/ui/src/message/MessageContent.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -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,
|
|
||||||
});
|
|
||||||
@@ -1,18 +1,9 @@
|
|||||||
import type { ReactNode } from 'react';
|
import { Message } from './Message';
|
||||||
|
|
||||||
import { show } from './base';
|
export const message = new Message({
|
||||||
import { Success } from './success';
|
anchorOrigin: {
|
||||||
|
vertical: 'top',
|
||||||
interface SuccessProps {
|
horizontal: 'center',
|
||||||
/**
|
|
||||||
* 自动关闭延时,单位毫秒。默认2000
|
|
||||||
*/
|
|
||||||
duration?: number;
|
|
||||||
content: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const message = {
|
|
||||||
success({ duration, content }: SuccessProps) {
|
|
||||||
return show({ Container: Success, duration, content });
|
|
||||||
},
|
},
|
||||||
};
|
autoHideDuration: 2000,
|
||||||
|
});
|
||||||
|
|||||||
@@ -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',
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { ReactNode } from 'react';
|
|
||||||
|
|
||||||
export interface ContainerProps {
|
|
||||||
content: ReactNode;
|
|
||||||
duration: number;
|
|
||||||
close: () => void;
|
|
||||||
}
|
|
||||||
62
libs/components/ui/src/notification/Notification.tsx
Normal file
62
libs/components/ui/src/notification/Notification.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
libs/components/ui/src/notification/index.ts
Normal file
1
libs/components/ui/src/notification/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './Notification';
|
||||||
Reference in New Issue
Block a user