@@ -3,8 +3,8 @@ import React, { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from '
3
3
4
4
import { Alert , AlertDescription } from '@/components/ui/alert' ;
5
5
import { Button } from '@/components/ui/button' ;
6
- import { Input } from '@/components/ui/input' ;
7
6
import { ScrollArea } from '@/components/ui/scroll-area' ;
7
+ import { Textarea } from '@/components/ui/textarea' ;
8
8
9
9
import { ChatMessage , ChatMessageType } from './chat-message' ;
10
10
@@ -28,32 +28,44 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
28
28
title,
29
29
} ) => {
30
30
const [ input , setInput ] = useState < string > ( '' ) ;
31
- const inputRef = useRef < HTMLInputElement > ( null ) ;
31
+ const inputRef = useRef < HTMLTextAreaElement > ( null ) ;
32
32
const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
33
33
34
34
const scrollToBottom = ( ) => {
35
35
messagesEndRef . current ?. scrollIntoView ( { behavior : 'smooth' } ) ;
36
36
} ;
37
37
38
+ // Focus and scroll to bottom on window open
38
39
useEffect ( ( ) => {
39
40
if ( isOpen ) {
40
41
inputRef . current ?. focus ( ) ;
41
42
scrollToBottom ( ) ;
42
43
}
43
44
} , [ isOpen ] ) ;
44
45
46
+ // Scroll to bottom on reception of messages
45
47
useEffect ( ( ) => {
46
48
scrollToBottom ( ) ;
47
49
} , [ messages , isLoading ] ) ;
48
50
51
+ // Resize textarea on input, up to a maximum height
52
+ useEffect ( ( ) => {
53
+ const textAreaEl = inputRef . current ;
54
+
55
+ if ( textAreaEl ) {
56
+ textAreaEl . style . height = 'auto' ;
57
+ textAreaEl . style . height = `${ Math . min ( textAreaEl . scrollHeight , 100 ) } px` ;
58
+ }
59
+ } , [ input ] ) ;
60
+
49
61
const handleSend = ( ) => {
50
62
if ( input . trim ( ) ) {
51
63
onSend ( input . trim ( ) ) ;
52
64
setInput ( '' ) ;
53
65
}
54
66
} ;
55
67
56
- const handleKeyPress = ( e : KeyboardEvent < HTMLInputElement > ) => {
68
+ const handleKeyPress = ( e : KeyboardEvent < HTMLTextAreaElement > ) => {
57
69
if ( e . key === 'Enter' && ! e . shiftKey ) {
58
70
e . preventDefault ( ) ;
59
71
handleSend ( ) ;
@@ -62,7 +74,10 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
62
74
63
75
return (
64
76
< div className = 'flex size-full flex-col' >
65
- < div className = 'bg-secondary/50 border-border flex items-center justify-between border-b px-4 py-3' >
77
+ < div
78
+ id = 'chat-header'
79
+ className = 'bg-secondary/50 border-border flex items-center justify-between border-b py-1 pl-3 pr-1'
80
+ >
66
81
< div className = 'flex items-center gap-2' >
67
82
< h2 className = 'font-semibold' > { title } </ h2 >
68
83
</ div >
@@ -78,7 +93,7 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
78
93
</ div >
79
94
</ div >
80
95
81
- < ScrollArea className = 'h-full flex-1 overflow-y-auto p-4' >
96
+ < ScrollArea id = 'chat-messages' className = 'h-full flex-1 overflow-y-auto p-4' >
82
97
{ messages . length === 0 && (
83
98
< div className = 'flex h-full flex-col items-center justify-center text-gray-500' >
84
99
< MessageSquare className = 'mb-4 size-12 opacity-50' />
@@ -103,18 +118,24 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
103
118
< div ref = { messagesEndRef } />
104
119
</ ScrollArea >
105
120
106
- < div className = 'bg-secondary/50 border-border border-t p-4' >
107
- < div className = 'flex gap-2' >
108
- < Input
121
+ < div className = 'bg-secondary/50 border-border border-t p-3' >
122
+ < div className = 'flex w-full items-center justify-between gap-3' >
123
+ < Textarea
124
+ rows = { 1 }
109
125
ref = { inputRef }
110
126
value = { input }
111
- onChange = { ( e : ChangeEvent < HTMLInputElement > ) => setInput ( e . target . value ) }
127
+ onChange = { ( e : ChangeEvent < HTMLTextAreaElement > ) => setInput ( e . target . value ) }
112
128
placeholder = 'Type your message...'
113
- onKeyPress = { handleKeyPress }
129
+ onKeyDown = { handleKeyPress }
114
130
disabled = { isLoading }
115
- className = 'bg-primary-foreground flex-1'
131
+ className = 'bg-primary-foreground scrollbar-thin scrollbar-track-black h-10 min-h-10 flex-1 resize-none overflow-y-auto p-2 '
116
132
/>
117
- < Button variant = 'outline' onClick = { handleSend } disabled = { isLoading || ! input . trim ( ) } >
133
+ < Button
134
+ variant = 'outline'
135
+ onClick = { handleSend }
136
+ disabled = { isLoading || ! input . trim ( ) }
137
+ className = 'min-h-10 px-3 py-2'
138
+ >
118
139
{ isLoading ? < Loader2 className = 'size-4 animate-spin' /> : < Send className = 'size-4' /> }
119
140
</ Button >
120
141
</ div >
0 commit comments