@@ -3,8 +3,8 @@ import React, { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from '
33
44import { Alert , AlertDescription } from '@/components/ui/alert' ;
55import { Button } from '@/components/ui/button' ;
6- import { Input } from '@/components/ui/input' ;
76import { ScrollArea } from '@/components/ui/scroll-area' ;
7+ import { Textarea } from '@/components/ui/textarea' ;
88
99import { ChatMessage , ChatMessageType } from './chat-message' ;
1010
@@ -28,32 +28,44 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
2828 title,
2929} ) => {
3030 const [ input , setInput ] = useState < string > ( '' ) ;
31- const inputRef = useRef < HTMLInputElement > ( null ) ;
31+ const inputRef = useRef < HTMLTextAreaElement > ( null ) ;
3232 const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
3333
3434 const scrollToBottom = ( ) => {
3535 messagesEndRef . current ?. scrollIntoView ( { behavior : 'smooth' } ) ;
3636 } ;
3737
38+ // Focus and scroll to bottom on window open
3839 useEffect ( ( ) => {
3940 if ( isOpen ) {
4041 inputRef . current ?. focus ( ) ;
4142 scrollToBottom ( ) ;
4243 }
4344 } , [ isOpen ] ) ;
4445
46+ // Scroll to bottom on reception of messages
4547 useEffect ( ( ) => {
4648 scrollToBottom ( ) ;
4749 } , [ messages , isLoading ] ) ;
4850
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+
4961 const handleSend = ( ) => {
5062 if ( input . trim ( ) ) {
5163 onSend ( input . trim ( ) ) ;
5264 setInput ( '' ) ;
5365 }
5466 } ;
5567
56- const handleKeyPress = ( e : KeyboardEvent < HTMLInputElement > ) => {
68+ const handleKeyPress = ( e : KeyboardEvent < HTMLTextAreaElement > ) => {
5769 if ( e . key === 'Enter' && ! e . shiftKey ) {
5870 e . preventDefault ( ) ;
5971 handleSend ( ) ;
@@ -62,7 +74,10 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
6274
6375 return (
6476 < 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+ >
6681 < div className = 'flex items-center gap-2' >
6782 < h2 className = 'font-semibold' > { title } </ h2 >
6883 </ div >
@@ -78,7 +93,7 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
7893 </ div >
7994 </ div >
8095
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' >
8297 { messages . length === 0 && (
8398 < div className = 'flex h-full flex-col items-center justify-center text-gray-500' >
8499 < MessageSquare className = 'mb-4 size-12 opacity-50' />
@@ -103,18 +118,24 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
103118 < div ref = { messagesEndRef } />
104119 </ ScrollArea >
105120
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 }
109125 ref = { inputRef }
110126 value = { input }
111- onChange = { ( e : ChangeEvent < HTMLInputElement > ) => setInput ( e . target . value ) }
127+ onChange = { ( e : ChangeEvent < HTMLTextAreaElement > ) => setInput ( e . target . value ) }
112128 placeholder = 'Type your message...'
113- onKeyPress = { handleKeyPress }
129+ onKeyDown = { handleKeyPress }
114130 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 '
116132 />
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+ >
118139 { isLoading ? < Loader2 className = 'size-4 animate-spin' /> : < Send className = 'size-4' /> }
119140 </ Button >
120141 </ div >
0 commit comments