@@ -9,18 +9,8 @@ import { VoiceInput } from './voice-input'
99
1010const PLACEHOLDER_MOBILE = 'Enter a message'
1111const PLACEHOLDER_DESKTOP = 'Enter a message or click the mic to speak'
12- const MAX_TEXTAREA_HEIGHT = 160 // Max height in pixels (e.g., for about 4-5 lines)
13-
14- const containerVariants = {
15- collapsed : {
16- height : '56px' , // Fixed height when collapsed
17- boxShadow : '0 1px 6px 0 rgba(0,0,0,0.05)' ,
18- } ,
19- expanded : {
20- height : 'auto' ,
21- boxShadow : '0 2px 10px 0 rgba(0,0,0,0.1)' ,
22- } ,
23- } as const
12+ const MAX_TEXTAREA_HEIGHT = 120 // Max height in pixels (e.g., for about 3-4 lines)
13+ const MAX_TEXTAREA_HEIGHT_MOBILE = 100 // Smaller for mobile
2414
2515export const ChatInput : React . FC < {
2616 onSubmit ?: ( value : string , isVoiceInput ?: boolean ) => void
@@ -45,8 +35,12 @@ export const ChatInput: React.FC<{
4535 el . style . height = 'auto' // Reset height to correctly calculate scrollHeight
4636 const scrollHeight = el . scrollHeight
4737
48- if ( scrollHeight > MAX_TEXTAREA_HEIGHT ) {
49- el . style . height = `${ MAX_TEXTAREA_HEIGHT } px`
38+ // Use mobile height on mobile devices, desktop height on desktop
39+ const isMobile = window . innerWidth < 768
40+ const maxHeight = isMobile ? MAX_TEXTAREA_HEIGHT_MOBILE : MAX_TEXTAREA_HEIGHT
41+
42+ if ( scrollHeight > maxHeight ) {
43+ el . style . height = `${ maxHeight } px`
5044 el . style . overflowY = 'auto'
5145 } else {
5246 el . style . height = `${ scrollHeight } px`
@@ -136,32 +130,28 @@ export const ChatInput: React.FC<{
136130
137131 return (
138132 < >
139- < div className = 'fixed right-0 bottom-0 left-0 flex w-full items-center justify-center bg-gradient-to-t from-white to-transparent pb-4 text-black' >
140- < motion . div
141- ref = { wrapperRef }
142- className = 'w-full max-w-3xl px-4'
143- variants = { containerVariants }
144- animate = { 'expanded' }
145- initial = 'collapsed'
146- style = { {
147- overflow : 'hidden' ,
148- borderRadius : 32 ,
149- background : '#fff' ,
150- border : '1px solid rgba(0,0,0,0.1)' ,
151- marginLeft : 'auto' ,
152- marginRight : 'auto' ,
153- } }
154- onClick = { handleActivate }
155- >
156- < div className = 'flex h-full w-full items-center rounded-full p-2' >
157- { /* Voice Input with Tooltip */ }
158- { isSttAvailable && (
159- < div className = 'mr-2' >
133+ < div className = 'fixed right-0 bottom-0 left-0 flex w-full items-center justify-center bg-gradient-to-t from-white to-transparent px-4 pb-4 text-black md:px-0 md:pb-4' >
134+ < div ref = { wrapperRef } className = 'w-full max-w-3xl md:max-w-[748px]' >
135+ { /* Text Input Area with Controls */ }
136+ < motion . div
137+ className = 'rounded-2xl border border-gray-200 bg-white shadow-sm md:rounded-3xl'
138+ onClick = { handleActivate }
139+ initial = { { opacity : 0 , y : 10 } }
140+ animate = { { opacity : 1 , y : 0 } }
141+ transition = { { duration : 0.2 } }
142+ >
143+ < div className = 'flex items-center gap-2 p-3 md:p-4' >
144+ { /* Voice Input */ }
145+ { isSttAvailable && (
160146 < TooltipProvider >
161147 < Tooltip >
162148 < TooltipTrigger asChild >
163149 < div >
164- < VoiceInput onVoiceStart = { handleVoiceStart } disabled = { isStreaming } />
150+ < VoiceInput
151+ onVoiceStart = { handleVoiceStart }
152+ disabled = { isStreaming }
153+ minimal
154+ />
165155 </ div >
166156 </ TooltipTrigger >
167157 < TooltipContent side = 'top' >
@@ -170,97 +160,87 @@ export const ChatInput: React.FC<{
170160 </ TooltipContent >
171161 </ Tooltip >
172162 </ TooltipProvider >
163+ ) }
164+
165+ { /* Text Input Container */ }
166+ < div className = 'relative flex-1' >
167+ < textarea
168+ ref = { textareaRef }
169+ value = { inputValue }
170+ onChange = { handleInputChange }
171+ className = 'flex w-full resize-none items-center overflow-hidden bg-transparent text-sm outline-none placeholder:text-gray-400 md:font-[330] md:text-base'
172+ placeholder = { isActive ? '' : '' }
173+ rows = { 1 }
174+ style = { {
175+ minHeight : window . innerWidth >= 768 ? '24px' : '28px' ,
176+ lineHeight : '1.4' ,
177+ paddingTop : window . innerWidth >= 768 ? '4px' : '3px' ,
178+ paddingBottom : window . innerWidth >= 768 ? '4px' : '3px' ,
179+ } }
180+ onKeyDown = { ( e ) => {
181+ if ( e . key === 'Enter' && ! e . shiftKey ) {
182+ e . preventDefault ( )
183+ handleSubmit ( )
184+ }
185+ } }
186+ />
187+
188+ { /* Placeholder */ }
189+ < div className = 'pointer-events-none absolute top-0 left-0 flex h-full w-full items-center' >
190+ { ! isActive && ! inputValue && (
191+ < >
192+ { /* Mobile placeholder */ }
193+ < div
194+ className = '-translate-y-1/2 absolute top-1/2 left-0 transform select-none text-gray-400 text-sm md:hidden md:text-base'
195+ style = { { paddingTop : '3px' , paddingBottom : '3px' } }
196+ >
197+ { PLACEHOLDER_MOBILE }
198+ </ div >
199+ { /* Desktop placeholder */ }
200+ < div
201+ className = '-translate-y-1/2 absolute top-1/2 left-0 hidden transform select-none font-[330] text-gray-400 text-sm md:block md:text-base'
202+ style = { { paddingTop : '4px' , paddingBottom : '4px' } }
203+ >
204+ { PLACEHOLDER_DESKTOP }
205+ </ div >
206+ </ >
207+ ) }
208+ </ div >
173209 </ div >
174- ) }
175210
176- { /* Text Input & Placeholder */ }
177- < div className = 'relative min-h-[40px] flex-1' >
178- < textarea
179- ref = { textareaRef }
180- value = { inputValue }
181- onChange = { handleInputChange }
182- className = 'w-full resize-none overflow-hidden bg-transparent px-3 py-3 text-base outline-none placeholder:text-gray-400'
183- placeholder = { isActive ? '' : '' }
184- rows = { 1 }
185- style = { {
186- minHeight : '40px' ,
187- lineHeight : '1.4' ,
188- } }
189- onKeyDown = { ( e ) => {
190- if ( e . key === 'Enter' && ! e . shiftKey ) {
191- e . preventDefault ( )
211+ { /* Send Button */ }
212+ < button
213+ className = { `flex items-center justify-center rounded-full p-1.5 text-white transition-colors md:p-2 ${
214+ inputValue . trim ( )
215+ ? 'bg-black hover:bg-zinc-700'
216+ : 'cursor-default bg-gray-300 hover:bg-gray-400'
217+ } `}
218+ title = { isStreaming ? 'Stop' : 'Send' }
219+ type = 'button'
220+ onClick = { ( e ) => {
221+ e . stopPropagation ( )
222+ if ( isStreaming ) {
223+ onStopStreaming ?.( )
224+ } else {
192225 handleSubmit ( )
193226 }
194227 } }
195- />
196-
197- < div className = 'pointer-events-none absolute top-0 left-0 flex h-full w-full items-center' >
198- { ! isActive && ! inputValue && (
228+ >
229+ { isStreaming ? (
230+ < >
231+ < Square size = { 16 } className = 'md:hidden' />
232+ < Square size = { 18 } className = 'hidden md:block' />
233+ </ >
234+ ) : (
199235 < >
200- { /* Mobile placeholder */ }
201- < div
202- className = '-translate-y-1/2 absolute top-1/2 left-3 select-none text-gray-400 md:hidden'
203- style = { {
204- whiteSpace : 'nowrap' ,
205- zIndex : 0 ,
206- background :
207- 'linear-gradient(90deg, rgba(150,150,150,0.2) 0%, rgba(150,150,150,0.8) 50%, rgba(150,150,150,0.2) 100%)' ,
208- backgroundSize : '200% 100%' ,
209- WebkitBackgroundClip : 'text' ,
210- WebkitTextFillColor : 'transparent' ,
211- animation : 'shimmer 10s infinite linear' ,
212- } }
213- >
214- { PLACEHOLDER_MOBILE }
215- </ div >
216- { /* Desktop placeholder */ }
217- < div
218- className = '-translate-y-1/2 absolute top-1/2 left-3 hidden select-none text-gray-400 md:block'
219- style = { {
220- whiteSpace : 'nowrap' ,
221- zIndex : 0 ,
222- background :
223- 'linear-gradient(90deg, rgba(150,150,150,0.2) 0%, rgba(150,150,150,0.8) 50%, rgba(150,150,150,0.2) 100%)' ,
224- backgroundSize : '200% 100%' ,
225- WebkitBackgroundClip : 'text' ,
226- WebkitTextFillColor : 'transparent' ,
227- animation : 'shimmer 10s infinite linear' ,
228- } }
229- >
230- { PLACEHOLDER_DESKTOP }
231- < style jsx global > { `
232- @keyframes shimmer {
233- 0% {
234- background-position: 200% 0;
235- }
236- 100% {
237- background-position: -200% 0;
238- }
239- }
240- ` } </ style >
241- </ div >
236+ < Send size = { 16 } className = 'md:hidden' />
237+ < Send size = { 18 } className = 'hidden md:block' />
242238 </ >
243239 ) }
244- </ div >
240+ </ button >
245241 </ div >
246-
247- < button
248- className = 'flex items-center justify-center rounded-full bg-black p-3 text-white hover:bg-zinc-700'
249- title = { isStreaming ? 'Stop' : 'Send' }
250- type = 'button'
251- onClick = { ( e ) => {
252- e . stopPropagation ( )
253- if ( isStreaming ) {
254- onStopStreaming ?.( )
255- } else {
256- handleSubmit ( )
257- }
258- } }
259- >
260- { isStreaming ? < Square size = { 18 } /> : < Send size = { 18 } /> }
261- </ button >
262- </ div >
263- </ motion . div >
242+ </ motion . div >
243+ </ div >
264244 </ div >
265245 </ >
266246 )
0 commit comments