81 lines
2.4 KiB
JavaScript
81 lines
2.4 KiB
JavaScript
import { toast as hotToast } from 'react-hot-toast';
|
|
|
|
/**
|
|
* Toast — transient confirmation of a user action.
|
|
*
|
|
* The app already depends on react-hot-toast (13 call sites), so this wraps it
|
|
* rather than introducing a second toast system. Wrapping buys three things:
|
|
* one place to set styling so toasts match the design system, a named API
|
|
* (`toast.success` / `toast.error` / `toast.promise`) instead of ad-hoc option
|
|
* objects, and a single default duration.
|
|
*
|
|
* Rule of thumb: toast for "that worked", `Alert` for "here is a condition you
|
|
* need to keep seeing", `ConfirmModal` for "are you sure".
|
|
*/
|
|
|
|
const BASE_STYLE = {
|
|
borderRadius: '12px',
|
|
background: '#FFFFFF',
|
|
color: 'hsl(215 25% 27%)',
|
|
fontSize: '13px',
|
|
fontWeight: 500,
|
|
padding: '10px 14px',
|
|
boxShadow: '0 12px 28px -6px rgb(16 24 40 / 0.12), 0 4px 10px -4px rgb(16 24 40 / 0.06)',
|
|
border: '1px solid hsl(150 8% 87%)',
|
|
maxWidth: '420px',
|
|
};
|
|
|
|
const DURATIONS = { success: 2600, error: 4200, info: 3000, loading: Infinity };
|
|
|
|
const withDefaults = (options = {}) => ({
|
|
position: 'bottom-center',
|
|
...options,
|
|
style: { ...BASE_STYLE, ...(options.style || {}) },
|
|
});
|
|
|
|
export const toast = {
|
|
success(message, options) {
|
|
return hotToast.success(message, {
|
|
duration: DURATIONS.success,
|
|
iconTheme: { primary: 'hsl(142 71% 36%)', secondary: '#FFFFFF' },
|
|
...withDefaults(options),
|
|
});
|
|
},
|
|
|
|
error(message, options) {
|
|
return hotToast.error(message, {
|
|
duration: DURATIONS.error,
|
|
iconTheme: { primary: 'hsl(0 84% 60%)', secondary: '#FFFFFF' },
|
|
...withDefaults(options),
|
|
});
|
|
},
|
|
|
|
info(message, options) {
|
|
return hotToast(message, { duration: DURATIONS.info, ...withDefaults(options) });
|
|
},
|
|
|
|
loading(message, options) {
|
|
return hotToast.loading(message, { duration: DURATIONS.loading, ...withDefaults(options) });
|
|
},
|
|
|
|
/**
|
|
* Ties a toast to an async action: loading while it runs, then success or
|
|
* error. Preferred over a manual loading/dismiss pair, which leaks a spinner
|
|
* whenever the promise rejects.
|
|
*/
|
|
promise(promise, messages, options) {
|
|
return hotToast.promise(
|
|
promise,
|
|
{
|
|
loading: messages?.loading ?? 'Working…',
|
|
success: messages?.success ?? 'Done',
|
|
error: messages?.error ?? 'Something went wrong',
|
|
},
|
|
withDefaults(options)
|
|
);
|
|
},
|
|
|
|
dismiss: hotToast.dismiss,
|
|
remove: hotToast.remove,
|
|
};
|