22
33import type {
44 ToastManagerAddOptions ,
5- ToastManagerPromiseOptions ,
65 ToastManagerUpdateOptions ,
76 ToastObject ,
87} from '@base-ui/react/toast'
@@ -11,20 +10,46 @@ import { useTranslation } from 'react-i18next'
1110import { cn } from '@/utils/classnames'
1211
1312type ToastData = Record < string , never >
14- type ToastType = 'success' | 'error' | 'warning' | 'info'
13+ type ToastToneStyle = {
14+ gradientClassName : string
15+ iconClassName : string
16+ }
17+
18+ const TOAST_TONE_STYLES = {
19+ success : {
20+ iconClassName : 'i-ri-checkbox-circle-fill text-text-success' ,
21+ gradientClassName : 'from-components-badge-status-light-success-halo to-background-gradient-mask-transparent' ,
22+ } ,
23+ error : {
24+ iconClassName : 'i-ri-error-warning-fill text-text-destructive' ,
25+ gradientClassName : 'from-components-badge-status-light-error-halo to-background-gradient-mask-transparent' ,
26+ } ,
27+ warning : {
28+ iconClassName : 'i-ri-alert-fill text-text-warning-secondary' ,
29+ gradientClassName : 'from-components-badge-status-light-warning-halo to-background-gradient-mask-transparent' ,
30+ } ,
31+ info : {
32+ iconClassName : 'i-ri-information-2-fill text-text-accent' ,
33+ gradientClassName : 'from-components-badge-status-light-normal-halo to-background-gradient-mask-transparent' ,
34+ } ,
35+ } satisfies Record < string , ToastToneStyle >
36+
37+ export type ToastType = keyof typeof TOAST_TONE_STYLES
1538
16- type ToastAddOptions = Omit < ToastManagerAddOptions < ToastData > , 'data' | 'positionerProps' | 'type' > & {
39+ export type ToastAddOptions = Omit < ToastManagerAddOptions < ToastData > , 'data' | 'positionerProps' | 'type' > & {
1740 type ?: ToastType
1841}
1942
20- type ToastUpdateOptions = Omit < ToastManagerUpdateOptions < ToastData > , 'data' | 'positionerProps' | 'type' > & {
43+ export type ToastUpdateOptions = Omit < ToastManagerUpdateOptions < ToastData > , 'data' | 'positionerProps' | 'type' > & {
2144 type ?: ToastType
2245}
2346
24- type ToastPromiseOptions < Value > = {
47+ type ToastPromiseResultOption < Value > = string | ToastUpdateOptions | ( ( value : Value ) => string | ToastUpdateOptions )
48+
49+ export type ToastPromiseOptions < Value > = {
2550 loading : string | ToastUpdateOptions
26- success : string | ToastUpdateOptions | ( ( result : Value ) => string | ToastUpdateOptions )
27- error : string | ToastUpdateOptions | ( ( error : unknown ) => string | ToastUpdateOptions )
51+ success : ToastPromiseResultOption < Value >
52+ error : ToastPromiseResultOption < unknown >
2853}
2954
3055export type ToastHostProps = {
@@ -34,6 +59,14 @@ export type ToastHostProps = {
3459
3560const toastManager = BaseToast . createToastManager < ToastData > ( )
3661
62+ function isToastType ( type : string ) : type is ToastType {
63+ return Object . prototype . hasOwnProperty . call ( TOAST_TONE_STYLES , type )
64+ }
65+
66+ function getToastType ( type ?: string ) : ToastType | undefined {
67+ return type && isToastType ( type ) ? type : undefined
68+ }
69+
3770export const toast = {
3871 add ( options : ToastAddOptions ) {
3972 return toastManager . add ( options )
@@ -45,43 +78,19 @@ export const toast = {
4578 toastManager . update ( toastId , options )
4679 } ,
4780 promise < Value > ( promiseValue : Promise < Value > , options : ToastPromiseOptions < Value > ) {
48- return toastManager . promise ( promiseValue , options as ToastManagerPromiseOptions < Value , ToastData > )
81+ return toastManager . promise ( promiseValue , options )
4982 } ,
5083}
5184
52- function ToastIcon ( { type } : { type ?: string } ) {
53- if ( type === 'success' ) {
54- return < span aria-hidden = "true" className = "i-ri-checkbox-circle-fill h-5 w-5 text-text-success" />
55- }
56-
57- if ( type === 'error' ) {
58- return < span aria-hidden = "true" className = "i-ri-error-warning-fill h-5 w-5 text-text-destructive" />
59- }
60-
61- if ( type === 'warning' ) {
62- return < span aria-hidden = "true" className = "i-ri-alert-fill h-5 w-5 text-text-warning-secondary" />
63- }
64-
65- if ( type === 'info' ) {
66- return < span aria-hidden = "true" className = "i-ri-information-2-fill h-5 w-5 text-text-accent" />
67- }
68-
69- return null
85+ function ToastIcon ( { type } : { type ?: ToastType } ) {
86+ return type
87+ ? < span aria-hidden = "true" className = { cn ( 'h-5 w-5' , TOAST_TONE_STYLES [ type ] . iconClassName ) } />
88+ : null
7089}
7190
72- function getToneGradientClasses ( type ?: string ) {
73- if ( type === 'success' )
74- return 'from-components-badge-status-light-success-halo to-background-gradient-mask-transparent'
75-
76- if ( type === 'error' )
77- return 'from-components-badge-status-light-error-halo to-background-gradient-mask-transparent'
78-
79- if ( type === 'warning' )
80- return 'from-components-badge-status-light-warning-halo to-background-gradient-mask-transparent'
81-
82- if ( type === 'info' )
83- return 'from-components-badge-status-light-normal-halo to-background-gradient-mask-transparent'
84-
91+ function getToneGradientClasses ( type ?: ToastType ) {
92+ if ( type )
93+ return TOAST_TONE_STYLES [ type ] . gradientClassName
8594 return 'from-background-default-subtle to-background-gradient-mask-transparent'
8695}
8796
@@ -93,12 +102,13 @@ function ToastCard({
93102 showHoverBridge ?: boolean
94103} ) {
95104 const { t } = useTranslation ( 'common' )
105+ const toastType = getToastType ( toastItem . type )
96106
97107 return (
98108 < BaseToast . Root
99109 toast = { toastItem }
100110 className = { cn (
101- 'pointer-events-auto absolute right-0 top-0 w-[360px] max-w-[calc(100vw-2rem)] origin-top-right cursor-default select-none outline -none' ,
111+ 'pointer-events-auto absolute right-0 top-0 w-[360px] max-w-[calc(100vw-2rem)] origin-top cursor-default select-none' ,
102112 '[--toast-current-height:var(--toast-frontmost-height,var(--toast-height))] [--toast-gap:8px] [--toast-peek:5px] [--toast-scale:calc(1-(var(--toast-index)*0.0225))] [--toast-shrink:calc(1-var(--toast-scale))]' ,
103113 '[height:var(--toast-current-height)] [z-index:calc(100-var(--toast-index))]' ,
104114 '[transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms] motion-reduce:transition-none' ,
@@ -110,11 +120,11 @@ function ToastCard({
110120 < div className = "relative overflow-hidden rounded-xl border border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]" >
111121 < div
112122 aria-hidden = "true"
113- className = { cn ( 'absolute inset-[-1px] bg-gradient-to-r opacity-40' , getToneGradientClasses ( toastItem . type ) ) }
123+ className = { cn ( 'absolute inset-[-1px] bg-gradient-to-r opacity-40' , getToneGradientClasses ( toastType ) ) }
114124 />
115125 < BaseToast . Content className = "relative flex items-start gap-1 overflow-hidden p-3 transition-opacity duration-200 data-[behind]:opacity-0 data-[expanded]:opacity-100" >
116126 < div className = "flex shrink-0 items-center justify-center p-0.5" >
117- < ToastIcon type = { toastItem . type } />
127+ < ToastIcon type = { toastType } />
118128 </ div >
119129 < div className = "min-w-0 flex-1 p-1" >
120130 < div className = "flex w-full items-center gap-1" >
0 commit comments