feat: textarea auto resize (#3695)

* feat: improve textarea user experience with autoresize

* chore: remove log

* chore: update test

* chore: update test and cleanup logic useEffect
This commit is contained in:
Faisal Amir 2024-09-19 10:10:30 +07:00 committed by GitHub
parent c62b6e9842
commit ba3c07eba8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 66 additions and 9 deletions

View File

@ -1,9 +1,8 @@
import React from 'react' import React from 'react'
import { render, screen } from '@testing-library/react' import { render, screen, act } from '@testing-library/react'
import '@testing-library/jest-dom' import '@testing-library/jest-dom'
import { TextArea } from './index' import { TextArea } from './index'
// Mock the styles import
jest.mock('./styles.scss', () => ({})) jest.mock('./styles.scss', () => ({}))
describe('@joi/core/TextArea', () => { describe('@joi/core/TextArea', () => {
@ -31,4 +30,40 @@ describe('@joi/core/TextArea', () => {
const textareaElement = screen.getByTestId('custom-textarea') const textareaElement = screen.getByTestId('custom-textarea')
expect(textareaElement).toHaveAttribute('rows', '5') expect(textareaElement).toHaveAttribute('rows', '5')
}) })
it('should auto resize the textarea based on minResize', () => {
render(<TextArea autoResize minResize={10} />)
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement
Object.defineProperty(textarea, 'scrollHeight', {
value: 20,
writable: true,
})
act(() => {
textarea.value = 'Short text'
textarea.dispatchEvent(new Event('input', { bubbles: true }))
})
expect(textarea.style.height).toBe('10px')
})
it('should auto resize the textarea based on maxResize', () => {
render(<TextArea autoResize maxResize={40} />)
const textarea = screen.getByRole('textbox') as HTMLTextAreaElement
Object.defineProperty(textarea, 'scrollHeight', {
value: 100,
writable: true,
})
act(() => {
textarea.value = 'A very long text that should exceed max height'
textarea.dispatchEvent(new Event('input', { bubbles: true }))
})
expect(textarea.style.height).toBe('40px')
})
}) })

View File

@ -1,19 +1,41 @@
import React, { ReactNode, forwardRef } from 'react' import React, { forwardRef, useRef, useEffect } from 'react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import './styles.scss' import './styles.scss'
import { ScrollArea } from '../ScrollArea'
type ResizeProps = {
autoResize?: boolean
minResize?: number
maxResize?: number
}
export interface TextAreaProps export interface TextAreaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} extends ResizeProps,
React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>( const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ className, ...props }, ref) => { (
{ autoResize, minResize = 80, maxResize = 250, className, ...props },
ref
) => {
const textareaRef = useRef<HTMLTextAreaElement>(null)
useEffect(() => {
if (autoResize && textareaRef.current) {
const textarea = textareaRef.current
textarea.style.height = 'auto'
const scrollHeight = textarea.scrollHeight
const newHeight = Math.min(maxResize, Math.max(minResize, scrollHeight))
textarea.style.height = `${newHeight}px`
textarea.style.overflow = newHeight >= maxResize ? 'auto' : 'hidden'
}
}, [props.value, autoResize, minResize, maxResize])
return ( return (
<div className="textarea__wrapper"> <div className="textarea__wrapper">
<textarea <textarea
className={twMerge('textarea', className)} className={twMerge('textarea', className)}
ref={ref} ref={autoResize ? textareaRef : ref}
{...props} {...props}
/> />
</div> </div>

View File

@ -36,7 +36,7 @@ const ModelConfigInput = ({
<TextArea <TextArea
placeholder={placeholder} placeholder={placeholder}
onChange={(e) => onValueChanged?.(e.target.value)} onChange={(e) => onValueChanged?.(e.target.value)}
cols={50} autoResize
value={value} value={value}
disabled={disabled} disabled={disabled}
/> />

View File

@ -247,7 +247,7 @@ const ThreadRightPanel = () => {
id="assistant-instructions" id="assistant-instructions"
placeholder="Eg. You are a helpful assistant." placeholder="Eg. You are a helpful assistant."
value={activeThread?.assistants[0].instructions ?? ''} value={activeThread?.assistants[0].instructions ?? ''}
rows={8} autoResize
onChange={onAssistantInstructionChanged} onChange={onAssistantInstructionChanged}
/> />
</div> </div>