[WEB-570] chore: toast refactor (#3836)
* new toast setup * chore: new toast implementation. * chore: move toast component to ui package. * chore: replace `setToast` with `setPromiseToast` in required places for better UX. * chore: code cleanup. * chore: update theme. * fix: theme switching issue. * chore: remove toast from issue update operations. * chore: add promise toast for add/ remove issue to cycle/ module and remove local spinners. --------- Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
parent
c06ef4d1d7
commit
53367a6bc4
167 changed files with 1827 additions and 1896 deletions
|
|
@ -198,6 +198,31 @@ module.exports = {
|
|||
300: convertToRGB("--color-onboarding-border-300"),
|
||||
},
|
||||
},
|
||||
toast: {
|
||||
text: {
|
||||
success: convertToRGB("--color-toast-success-text"),
|
||||
error: convertToRGB("--color-toast-error-text"),
|
||||
warning: convertToRGB("--color-toast-warning-text"),
|
||||
info: convertToRGB("--color-toast-info-text"),
|
||||
loading: convertToRGB("--color-toast-loading-text"),
|
||||
secondary: convertToRGB("--color-toast-secondary-text"),
|
||||
tertiary: convertToRGB("--color-toast-tertiary-text"),
|
||||
},
|
||||
background: {
|
||||
success: convertToRGB("--color-toast-success-background"),
|
||||
error: convertToRGB("--color-toast-error-background"),
|
||||
warning: convertToRGB("--color-toast-warning-background"),
|
||||
info: convertToRGB("--color-toast-info-background"),
|
||||
loading: convertToRGB("--color-toast-loading-background"),
|
||||
},
|
||||
border: {
|
||||
success: convertToRGB("--color-toast-success-border"),
|
||||
error: convertToRGB("--color-toast-error-border"),
|
||||
warning: convertToRGB("--color-toast-warning-border"),
|
||||
info: convertToRGB("--color-toast-info-border"),
|
||||
loading: convertToRGB("--color-toast-loading-border"),
|
||||
},
|
||||
},
|
||||
},
|
||||
keyframes: {
|
||||
leftToaster: {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"react-color": "^2.19.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-popper": "^2.3.0",
|
||||
"sonner": "^1.4.2",
|
||||
"tailwind-merge": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ export * from "./spinners";
|
|||
export * from "./tooltip";
|
||||
export * from "./loader";
|
||||
export * from "./control-link";
|
||||
export * from "./toast";
|
||||
|
|
|
|||
35
packages/ui/src/spinners/circular-bar-spinner.tsx
Normal file
35
packages/ui/src/spinners/circular-bar-spinner.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import * as React from "react";
|
||||
|
||||
interface ICircularBarSpinner extends React.SVGAttributes<SVGElement> {
|
||||
height?: string;
|
||||
width?: string;
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
export const CircularBarSpinner: React.FC<ICircularBarSpinner> = ({
|
||||
height = "16px",
|
||||
width = "16px",
|
||||
className = "",
|
||||
}) => (
|
||||
<div role="status">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" className={className}>
|
||||
<g>
|
||||
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.14} />
|
||||
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.29} transform="rotate(30 12 12)" />
|
||||
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.43} transform="rotate(60 12 12)" />
|
||||
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.57} transform="rotate(90 12 12)" />
|
||||
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.71} transform="rotate(120 12 12)" />
|
||||
<rect width={2} height={5} x={11} y={1} fill="currentColor" opacity={0.86} transform="rotate(150 12 12)" />
|
||||
<rect width={2} height={5} x={11} y={1} fill="currentColor" transform="rotate(180 12 12)" />
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
calcMode="discrete"
|
||||
dur="0.75s"
|
||||
repeatCount="indefinite"
|
||||
type="rotate"
|
||||
values="0 12 12;30 12 12;60 12 12;90 12 12;120 12 12;150 12 12;180 12 12;210 12 12;240 12 12;270 12 12;300 12 12;330 12 12;360 12 12"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from "./circular-spinner";
|
||||
export * from "./circular-bar-spinner";
|
||||
|
|
|
|||
206
packages/ui/src/toast/index.tsx
Normal file
206
packages/ui/src/toast/index.tsx
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import * as React from "react";
|
||||
import { Toaster, toast } from "sonner";
|
||||
// icons
|
||||
import { AlertTriangle, CheckCircle2, X, XCircle } from "lucide-react";
|
||||
// spinner
|
||||
import { CircularBarSpinner } from "../spinners";
|
||||
// helper
|
||||
import { cn } from "../../helpers";
|
||||
|
||||
export enum TOAST_TYPE {
|
||||
SUCCESS = "success",
|
||||
ERROR = "error",
|
||||
INFO = "info",
|
||||
WARNING = "warning",
|
||||
LOADING = "loading",
|
||||
}
|
||||
|
||||
type SetToastProps =
|
||||
| {
|
||||
type: TOAST_TYPE.LOADING;
|
||||
title?: string;
|
||||
}
|
||||
| {
|
||||
id?: string | number;
|
||||
type: Exclude<TOAST_TYPE, TOAST_TYPE.LOADING>;
|
||||
title: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type PromiseToastCallback<ToastData> = (data: ToastData) => string;
|
||||
|
||||
type PromiseToastData<ToastData> = {
|
||||
title: string;
|
||||
message?: PromiseToastCallback<ToastData>;
|
||||
};
|
||||
|
||||
type PromiseToastOptions<ToastData> = {
|
||||
loading?: string;
|
||||
success: PromiseToastData<ToastData>;
|
||||
error: PromiseToastData<ToastData>;
|
||||
};
|
||||
|
||||
type ToastContentProps = {
|
||||
toastId: string | number;
|
||||
icon?: React.ReactNode;
|
||||
textColorClassName: string;
|
||||
backgroundColorClassName: string;
|
||||
borderColorClassName: string;
|
||||
};
|
||||
|
||||
type ToastProps = {
|
||||
theme: "light" | "dark" | "system";
|
||||
};
|
||||
|
||||
export const Toast = (props: ToastProps) => {
|
||||
const { theme } = props;
|
||||
return <Toaster visibleToasts={5} gap={20} theme={theme} />;
|
||||
};
|
||||
|
||||
export const setToast = (props: SetToastProps) => {
|
||||
const renderToastContent = ({
|
||||
toastId,
|
||||
icon,
|
||||
textColorClassName,
|
||||
backgroundColorClassName,
|
||||
borderColorClassName,
|
||||
}: ToastContentProps) =>
|
||||
props.type === TOAST_TYPE.LOADING ? (
|
||||
<div
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
className={cn("w-[350px] h-[67.3px] rounded-lg border shadow-sm p-2", backgroundColorClassName, borderColorClassName)}
|
||||
>
|
||||
<div className="w-full h-full flex items-center justify-center px-4 py-2">
|
||||
{icon && <div className="flex items-center justify-center">{icon}</div>}
|
||||
<div className={cn("w-full flex items-center gap-0.5 pr-1", icon ? "pl-4" : "pl-1")}>
|
||||
<div className={cn("grow text-sm font-semibold", textColorClassName)}>{props.title ?? "Loading..."}</div>
|
||||
<div className="flex-shrink-0">
|
||||
<X
|
||||
className="text-toast-text-secondary hover:text-toast-text-tertiary cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
width={14}
|
||||
height={14}
|
||||
onClick={() => toast.dismiss(toastId)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
className={cn(
|
||||
"relative flex flex-col w-[350px] rounded-lg border shadow-sm p-2",
|
||||
backgroundColorClassName,
|
||||
borderColorClassName
|
||||
)}
|
||||
>
|
||||
<X
|
||||
className="fixed top-2 right-2.5 text-toast-text-secondary hover:text-toast-text-tertiary cursor-pointer"
|
||||
strokeWidth={1.5}
|
||||
width={14}
|
||||
height={14}
|
||||
onClick={() => toast.dismiss(toastId)}
|
||||
/>
|
||||
<div className="w-full flex items-center px-4 py-2">
|
||||
{icon && <div className="flex items-center justify-center">{icon}</div>}
|
||||
<div className={cn("flex flex-col gap-0.5 pr-1", icon ? "pl-6" : "pl-1")}>
|
||||
<div className={cn("text-sm font-semibold", textColorClassName)}>{props.title}</div>
|
||||
{props.message && <div className="text-toast-text-secondary text-xs font-medium">{props.message}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
switch (props.type) {
|
||||
case TOAST_TYPE.SUCCESS:
|
||||
return toast.custom(
|
||||
(toastId) =>
|
||||
renderToastContent({
|
||||
toastId,
|
||||
icon: <CheckCircle2 width={28} height={28} strokeWidth={1.5} className="text-toast-text-success" />,
|
||||
textColorClassName: "text-toast-text-success",
|
||||
backgroundColorClassName: "bg-toast-background-success",
|
||||
borderColorClassName: "border-toast-border-success",
|
||||
}),
|
||||
props.id ? { id: props.id } : {}
|
||||
);
|
||||
case TOAST_TYPE.ERROR:
|
||||
return toast.custom(
|
||||
(toastId) =>
|
||||
renderToastContent({
|
||||
toastId,
|
||||
icon: <XCircle width={28} height={28} strokeWidth={1.5} className="text-toast-text-error" />,
|
||||
textColorClassName: "text-toast-text-error",
|
||||
backgroundColorClassName: "bg-toast-background-error",
|
||||
borderColorClassName: "border-toast-border-error",
|
||||
}),
|
||||
props.id ? { id: props.id } : {}
|
||||
);
|
||||
case TOAST_TYPE.WARNING:
|
||||
return toast.custom(
|
||||
(toastId) =>
|
||||
renderToastContent({
|
||||
toastId,
|
||||
icon: <AlertTriangle width={28} height={28} strokeWidth={1.5} className="text-toast-text-warning" />,
|
||||
textColorClassName: "text-toast-text-warning",
|
||||
backgroundColorClassName: "bg-toast-background-warning",
|
||||
borderColorClassName: "border-toast-border-warning",
|
||||
}),
|
||||
props.id ? { id: props.id } : {}
|
||||
);
|
||||
case TOAST_TYPE.INFO:
|
||||
return toast.custom(
|
||||
(toastId) =>
|
||||
renderToastContent({
|
||||
toastId,
|
||||
textColorClassName: "text-toast-text-info",
|
||||
backgroundColorClassName: "bg-toast-background-info",
|
||||
borderColorClassName: "border-toast-border-info",
|
||||
}),
|
||||
props.id ? { id: props.id } : {}
|
||||
);
|
||||
|
||||
case TOAST_TYPE.LOADING:
|
||||
return toast.custom((toastId) =>
|
||||
renderToastContent({
|
||||
toastId,
|
||||
icon: <CircularBarSpinner className="text-toast-text-tertiary" />,
|
||||
textColorClassName: "text-toast-text-loading",
|
||||
backgroundColorClassName: "bg-toast-background-loading",
|
||||
borderColorClassName: "border-toast-border-loading",
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const setPromiseToast = <ToastData,>(
|
||||
promise: Promise<ToastData>,
|
||||
options: PromiseToastOptions<ToastData>
|
||||
): void => {
|
||||
const tId = setToast({ type: TOAST_TYPE.LOADING, title: options.loading });
|
||||
|
||||
promise
|
||||
.then((data: ToastData) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
id: tId,
|
||||
title: options.success.title,
|
||||
message: options.success.message?.(data),
|
||||
});
|
||||
})
|
||||
.catch((data: ToastData) => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
id: tId,
|
||||
title: options.error.title,
|
||||
message: options.error.message?.(data),
|
||||
});
|
||||
});
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue