add basic toaster

This commit is contained in:
Nate Kelley 2025-02-21 15:26:57 -07:00
parent 59beb494c4
commit c301821c4f
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
6 changed files with 129 additions and 65 deletions

12
web/package-lock.json generated
View File

@ -87,6 +87,7 @@
"react-virtualized-auto-sizer": "^1.0.25",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.1",
"split-pane-react": "^0.1.3",
"tailwind-merge": "^3.0.1",
"utility-types": "^3.11.0",
@ -2412,6 +2413,7 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
"extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {
@ -23496,6 +23498,16 @@
"node": ">=10.0.0"
}
},
"node_modules/sonner": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.1.tgz",
"integrity": "sha512-FRBphaehZ5tLdLcQ8g2WOIRE+Y7BCfWi5Zyd8bCvBjiW8TxxAyoWZIxS661Yz6TGPqFQ4VLzOF89WEYhfynSFQ==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",

View File

@ -96,6 +96,7 @@
"react-virtualized-auto-sizer": "^1.0.25",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.1",
"split-pane-react": "^0.1.3",
"tailwind-merge": "^3.0.1",
"utility-types": "^3.11.0",

View File

@ -0,0 +1,30 @@
'use client';
import { useTheme } from 'next-themes';
import { Toaster } from 'sonner';
type ToasterProps = React.ComponentProps<typeof Toaster>;
const AppToaster = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme();
return (
<Toaster
position="top-center"
theme={theme as ToasterProps['theme']}
className="toaster group"
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'group-[.toast]:text-muted-foreground',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground'
}
}}
{...props}
/>
);
};
export { AppToaster };

View File

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

View File

@ -1,6 +1,7 @@
import React, { PropsWithChildren } from 'react';
import { App, ModalFuncProps } from 'antd';
import { createStyles } from 'antd-style';
// import { App, ModalFuncProps } from 'antd';
import { toast, type ExternalToast } from 'sonner';
// import { createStyles } from 'antd-style';
import { useMemoizedFn } from 'ahooks';
import {
useContextSelector,
@ -18,39 +19,57 @@ export interface NotificationProps {
duration?: number;
}
const useStyles = createStyles(({ token, css }) => ({
modal: css`
.busterv2-modal-body {
padding: 0px !important;
}
// const useStyles = createStyles(({ token, css }) => ({
// modal: css`
// .busterv2-modal-body {
// padding: 0px !important;
// }
.busterv2-modal-confirm-body {
padding: 24px 32px 16px 32px !important;
}
// .busterv2-modal-confirm-body {
// padding: 24px 32px 16px 32px !important;
// }
.busterv2-modal-confirm-btns {
margin-top: 0px !important;
padding: 12px 32px !important;
border-top: 0.5px solid ${token.colorBorder};
display: flex;
align-items: center;
justify-content: flex-end;
}
// .busterv2-modal-confirm-btns {
// margin-top: 0px !important;
// padding: 12px 32px !important;
// border-top: 0.5px solid ${token.colorBorder};
// display: flex;
// align-items: center;
// justify-content: flex-end;
// }
.busterv2-modal-confirm-content {
color: ${token.colorTextSecondary} !important;
}
`
}));
// .busterv2-modal-confirm-content {
// color: ${token.colorTextSecondary} !important;
// }
// `
// }));
export const useBusterNotificationsInternal = () => {
const { message, notification, modal } = App.useApp();
const { cx, styles } = useStyles();
// const { message, notification, modal } = App.useApp();
// const { cx, styles } = useStyles();
const openNotification = useMemoizedFn(
(props: { title?: string; message: string; type: NotificationType }) => {
notification?.open?.({ ...props, description: props.message, message: props.title });
const { title, message, type } = props;
const toastOptions: ExternalToast = {
description: message,
position: 'top-center'
};
switch (type) {
case 'success':
return toast.success(title, toastOptions);
case 'info':
return toast.info(title, toastOptions);
case 'warning':
return toast.warning(title, toastOptions);
case 'error':
return toast.error(title, toastOptions);
default:
const _never: never = type;
return '';
}
}
);
@ -59,24 +78,24 @@ export const useBusterNotificationsInternal = () => {
const type = values.type || 'error';
const title = values.title || 'Error';
const message = values.message || 'Something went wrong. Please try again.';
openNotification({ ...values, message, title, type });
return openNotification({ ...values, message, title, type });
});
const openInfoNotification = useMemoizedFn(
({ type = 'info', message = 'Info', title = 'Info', ...props }: NotificationProps) => {
openNotification({ ...props, title, message, type });
return openNotification({ ...props, title, message, type });
}
);
const openSuccessNotification = useMemoizedFn(
({ type = 'success', title = 'Success', message = 'success', ...props }: NotificationProps) => {
openNotification({ ...props, message, title, type });
return openNotification({ ...props, message, title, type });
}
);
const openWarningNotification = useMemoizedFn(
({ type = 'warning', title = 'Warning', message = 'Warning', ...props }: NotificationProps) => {
openNotification({ ...props, message, title, type });
return openNotification({ ...props, message, title, type });
}
);
@ -86,28 +105,27 @@ export const useBusterNotificationsInternal = () => {
(props: {
type: NotificationType;
message: string;
loading?: boolean;
onClose?: () => void;
duration?: number;
}) => {
if (props.loading) {
message.loading(props.message, props.duration, props.onClose);
} else {
message?.[props.type]?.(props.message, props.duration, props.onClose);
}
return openNotification({
...props,
title: props.message,
message: ''
});
}
);
const openErrorMessage = useMemoizedFn((message: string) => {
openMessage({ type: 'error', message });
return openMessage({ type: 'error', message });
});
const openInfoMessage = useMemoizedFn((message: string, duration?: number) => {
openMessage({ type: 'info', message, duration });
return openMessage({ type: 'info', message, duration });
});
const openSuccessMessage = useMemoizedFn((message: string) => {
openMessage({ type: 'success', message });
return openMessage({ type: 'success', message });
});
const openConfirmModal = useMemoizedFn(
@ -117,36 +135,34 @@ export const useBusterNotificationsInternal = () => {
onOk: () => void;
onCancel?: () => void;
icon?: React.ReactNode;
okButtonProps?: ModalFuncProps['okButtonProps'];
cancelButtonProps?: ModalFuncProps['cancelButtonProps'];
width?: string | number;
useReject?: boolean;
}): Promise<void> => {
const useReject = props.useReject ?? true;
return new Promise((resolve, reject) => {
modal.confirm({
icon: props.icon || <></>,
...props,
className: cx(styles.modal, ''),
cancelButtonProps: {
...props.cancelButtonProps,
type: 'text'
},
okButtonProps: {
...props.okButtonProps,
type: 'default'
},
onOk: async () => {
await props.onOk();
resolve();
},
onCancel: async () => {
await props.onCancel?.();
if (useReject) reject();
else resolve();
}
});
// modal.confirm({
// icon: props.icon || <></>,
// ...props,
// className: cx(styles.modal, ''),
// cancelButtonProps: {
// ...props.cancelButtonProps,
// type: 'text'
// },
// okButtonProps: {
// ...props.okButtonProps,
// type: 'default'
// },
// onOk: async () => {
// await props.onOk();
// resolve();
// },
// onCancel: async () => {
// await props.onCancel?.();
// if (useReject) reject();
// else resolve();
// }
// });
});
}
);

View File

@ -9,6 +9,7 @@ import {
createContext,
ContextSelector
} from '@fluentui/react-context-selector';
import { AppToaster } from '@/components/ui/toaster';
export const ENABLE_DARK_MODE = false;
@ -31,7 +32,10 @@ export const BusterStyleProvider: React.FC<PropsWithChildren<{}>> = ({ children
enableSystem={ENABLE_DARK_MODE}
themes={['light', 'dark']}
disableTransitionOnChange>
<BaseBusterStyleProvider>{children}</BaseBusterStyleProvider>
<BaseBusterStyleProvider>
{children}
<AppToaster />
</BaseBusterStyleProvider>
</NextThemeProvider>
);
};