@@ -76,6 +76,7 @@ export function ChatBar(props: {
7676 const [ images , setImages ] = useState <
7777 Array < { file : File ; b64 : string | undefined } >
7878 > ( [ ] ) ;
79+ const [ isDragOver , setIsDragOver ] = useState ( false ) ;
7980
8081 function handleSubmit ( message : string ) {
8182 const userMessage : NebulaUserMessage = {
@@ -104,10 +105,35 @@ export function ChatBar(props: {
104105 } ,
105106 } ) ;
106107
107- async function handleImageUpload ( images : File [ ] ) {
108+ async function handleImageUpload ( files : File [ ] ) {
109+ const totalFiles = files . length + images . length ;
110+
111+ if ( totalFiles > maxAllowedImagesPerMessage ) {
112+ toast . error (
113+ `You can only upload up to ${ maxAllowedImagesPerMessage } images at a time` ,
114+ {
115+ position : "top-right" ,
116+ }
117+ ) ;
118+ return ;
119+ }
120+
121+ const validFiles : File [ ] = [ ] ;
122+
123+ for ( const file of files ) {
124+ if ( file . size <= 5 * 1024 * 1024 ) {
125+ validFiles . push ( file ) ;
126+ } else {
127+ toast . error ( "Image is larger than 5MB" , {
128+ description : `File: ${ file . name } ` ,
129+ position : "top-right" ,
130+ } ) ;
131+ }
132+ }
133+
108134 try {
109135 const urls = await Promise . all (
110- images . map ( async ( image ) => {
136+ validFiles . map ( async ( image ) => {
111137 const b64 = await uploadImageMutation . mutateAsync ( image ) ;
112138 return { file : image , b64 : b64 } ;
113139 } )
@@ -126,16 +152,39 @@ export function ChatBar(props: {
126152 < DynamicHeight transition = "height 200ms ease" >
127153 < div
128154 className = { cn (
129- "overflow-hidden rounded-2xl border border-border bg-card" ,
155+ "overflow-hidden rounded-2xl border border-border bg-card transition-colors" ,
156+ isDragOver &&
157+ props . allowImageUpload &&
158+ "border-nebula-pink-foreground bg-nebula-pink/5" ,
130159 props . className
131160 ) }
132161 onDrop = { ( e ) => {
133162 e . preventDefault ( ) ;
163+ setIsDragOver ( false ) ;
134164 if ( ! props . allowImageUpload ) return ;
135165 const files = Array . from ( e . dataTransfer . files ) ;
136166 if ( files . length > 0 ) handleImageUpload ( files ) ;
137167 } }
138- onDragOver = { ( e ) => e . preventDefault ( ) }
168+ onDragOver = { ( e ) => {
169+ e . preventDefault ( ) ;
170+ if ( props . allowImageUpload ) {
171+ setIsDragOver ( true ) ;
172+ }
173+ } }
174+ onDragEnter = { ( e ) => {
175+ e . preventDefault ( ) ;
176+ if ( props . allowImageUpload ) {
177+ setIsDragOver ( true ) ;
178+ }
179+ } }
180+ onDragLeave = { ( e ) => {
181+ e . preventDefault ( ) ;
182+ // Only set drag over to false if we're leaving the container entirely
183+ if ( ! e . currentTarget . contains ( e . relatedTarget as Node ) ) {
184+ setIsDragOver ( false ) ;
185+ }
186+ } }
187+ aria-dropeffect = { props . allowImageUpload ? "copy" : "none" }
139188 >
140189 { images . length > 0 && (
141190 < ImagePreview
@@ -274,32 +323,7 @@ export function ChatBar(props: {
274323 value = { undefined }
275324 accept = "image/jpeg,image/png,image/webp"
276325 onChange = { ( files ) => {
277- const totalFiles = files . length + images . length ;
278-
279- if ( totalFiles > maxAllowedImagesPerMessage ) {
280- toast . error (
281- `You can only upload up to ${ maxAllowedImagesPerMessage } images at a time` ,
282- {
283- position : "top-right" ,
284- }
285- ) ;
286- return ;
287- }
288-
289- const validFiles : File [ ] = [ ] ;
290-
291- for ( const file of files ) {
292- if ( file . size <= 5 * 1024 * 1024 ) {
293- validFiles . push ( file ) ;
294- } else {
295- toast . error ( "Image is larger than 5MB" , {
296- description : `File: ${ file . name } ` ,
297- position : "top-right" ,
298- } ) ;
299- }
300- }
301-
302- handleImageUpload ( validFiles ) ;
326+ handleImageUpload ( files ) ;
303327 } }
304328 variant = "ghost"
305329 className = "!h-auto w-auto shrink-0 gap-2 p-2"
0 commit comments