11'use client' ;
22
3- import { ReactNode } from 'react' ;
3+ import React from 'react' ;
44import { LuCircleAlert , LuCircleCheck , LuX } from 'react-icons/lu' ;
55import { Toaster as Sonner , toast as sonnerToast } from 'sonner' ;
66
7+ import { cn } from '../lib/utils' ;
78import { Button } from './Button' ;
89
910export const Toast = ( ) => {
@@ -15,9 +16,13 @@ export const Toast = () => {
1516 visibleToasts = { 3 }
1617 duration = { 3000 }
1718 icons = { {
18- success : < LuCircleCheck className = "size-6 text-functional-green" /> ,
19- close : < LuX className = "size-6 text-neutral-black" /> ,
20- error : < LuCircleAlert className = "size-6 text-functional-red" /> ,
19+ success : (
20+ < LuCircleCheck className = "size-6 shrink-0 text-functional-green" />
21+ ) ,
22+ close : < LuX className = "size-6 shrink-0 text-neutral-black" /> ,
23+ error : (
24+ < LuCircleAlert className = "size-6 shrink-0 text-functional-red" />
25+ ) ,
2126 } }
2227 toastOptions = { {
2328 classNames : {
@@ -39,13 +44,20 @@ const ToastWrapper = ({
3944 id,
4045 dismissable = false ,
4146 children,
47+ isSingleLine = false ,
4248} : {
4349 id : string | number ;
4450 dismissable ?: boolean ;
4551 children : React . ReactNode ;
52+ isSingleLine ?: boolean ;
4653} ) => {
4754 return (
48- < div className = "flex w-full items-start gap-2" >
55+ < div
56+ className = { cn (
57+ 'flex w-full items-start gap-2' ,
58+ isSingleLine && 'items-center' ,
59+ ) }
60+ >
4961 { children }
5062 { dismissable && (
5163 < Button
@@ -60,14 +72,55 @@ const ToastWrapper = ({
6072 ) ;
6173} ;
6274
63- const ToastBody = ( { children } : { children : React . ReactNode } ) => {
75+ const ToastBody = ( {
76+ children,
77+ isSingleLine = false ,
78+ } : {
79+ children : React . ReactNode ;
80+ isSingleLine ?: boolean ;
81+ } ) => {
82+ if ( isSingleLine ) {
83+ return (
84+ < div className = "flex w-full min-w-0 items-center gap-2 text-base text-neutral-charcoal" >
85+ { children }
86+ </ div >
87+ ) ;
88+ }
89+
6490 return (
6591 < div className = "flex w-full flex-col gap-2 px-1 pt-1 text-base text-neutral-charcoal" >
6692 { children }
6793 </ div >
6894 ) ;
6995} ;
7096
97+ const ToastActions = ( {
98+ children,
99+ isSingleLine = false ,
100+ } : {
101+ children : React . ReactNode ;
102+ isSingleLine ?: boolean ;
103+ } ) => {
104+ const renderActions = ( ) => {
105+ if ( Array . isArray ( children ) ) {
106+ return children . map ( ( action , index ) => (
107+ < React . Fragment key = { index } > { action } </ React . Fragment >
108+ ) ) ;
109+ }
110+ return children ;
111+ } ;
112+
113+ if ( isSingleLine ) {
114+ return (
115+ < div className = "ml-auto flex shrink-0 items-center gap-2" >
116+ { renderActions ( ) }
117+ </ div >
118+ ) ;
119+ }
120+
121+ return < div className = "mt-2 flex gap-4" > { renderActions ( ) } </ div > ;
122+ } ;
123+
71124const ToastTitle = ( {
72125 children,
73126} : {
@@ -81,25 +134,53 @@ const ToastTitle = ({
81134 ) ;
82135} ;
83136
137+ const SingleLineMessage = ( {
138+ className,
139+ children,
140+ } : {
141+ className ?: string ;
142+ children : React . ReactNode ;
143+ } ) => {
144+ return (
145+ < span className = { cn ( 'min-w-0 flex-1 truncate' , className ) } > { children } </ span >
146+ ) ;
147+ } ;
148+
84149export const toast = {
85150 success : ( {
86151 title,
87152 message,
88153 dismissable,
89154 children,
155+ actions,
90156 } : {
91157 title ?: string ;
92- message ?: ReactNode ;
158+ message ?: React . ReactNode ;
93159 dismissable ?: boolean ;
94160 children ?: React . ReactNode ;
161+ actions ?: [ React . ReactNode , React . ReactNode ?] ;
95162 } ) => {
163+ const isSingleLine = ! title ;
164+
96165 return sonnerToast . custom ( ( id ) => (
97- < ToastWrapper id = { id } dismissable = { dismissable } >
98- < LuCircleCheck className = "size-6 stroke-1 text-functional-green" />
99- < ToastBody >
100- { title ? < ToastTitle > { title } </ ToastTitle > : null }
101- { message ? < div > { message } </ div > : null }
166+ < ToastWrapper
167+ id = { id }
168+ dismissable = { dismissable }
169+ isSingleLine = { isSingleLine }
170+ >
171+ < LuCircleCheck className = "size-6 shrink-0 stroke-1 text-functional-green" />
172+ < ToastBody isSingleLine = { isSingleLine } >
173+ { title && < ToastTitle > { title } </ ToastTitle > }
174+ { message &&
175+ ( isSingleLine ? (
176+ < SingleLineMessage > { message } </ SingleLineMessage >
177+ ) : (
178+ < span > { message } </ span >
179+ ) ) }
102180 { children }
181+ { actions && (
182+ < ToastActions isSingleLine = { isSingleLine } > { actions } </ ToastActions >
183+ ) }
103184 </ ToastBody >
104185 </ ToastWrapper >
105186 ) ) ;
@@ -110,25 +191,43 @@ export const toast = {
110191 message,
111192 children,
112193 dismissable = true ,
194+ actions,
113195 } : {
114196 title ?: string ;
115- message ?: string ;
197+ message ?: React . ReactNode ;
116198 children ?: React . ReactNode ;
117199 dismissable ?: boolean ;
200+ actions ?: [ React . ReactNode , React . ReactNode ?] ;
118201 } ) => {
202+ const isSingleLine = ! title ;
203+
119204 // TODO: some odd behavior with Tailwind text-white an text-title-base conflicting here (the size gets stripped by the compiler).
120205 return sonnerToast . custom (
121206 ( id ) => (
122- < ToastWrapper id = { id } dismissable = { dismissable } >
123- < LuCircleAlert className = "size-6 stroke-1 text-white" />
124- < ToastBody >
125- { title ? (
207+ < ToastWrapper
208+ id = { id }
209+ dismissable = { dismissable }
210+ isSingleLine = { isSingleLine }
211+ >
212+ < LuCircleAlert className = "size-6 shrink-0 stroke-1 text-white" />
213+ < ToastBody isSingleLine = { isSingleLine } >
214+ { title && (
126215 < ToastTitle >
127216 < span className = "text-white" > { title } </ span >
128217 </ ToastTitle >
129- ) : null }
130- { message ? < div className = "text-white" > { message } </ div > : null }
218+ ) }
219+ { message &&
220+ ( isSingleLine ? (
221+ < SingleLineMessage className = "text-white" >
222+ { message }
223+ </ SingleLineMessage >
224+ ) : (
225+ < div className = "text-white" > { message } </ div >
226+ ) ) }
131227 { children }
228+ { actions && (
229+ < ToastActions isSingleLine = { isSingleLine } > { actions } </ ToastActions >
230+ ) }
132231 </ ToastBody >
133232 </ ToastWrapper >
134233 ) ,
0 commit comments