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 { render, screen } from '@testing-library/react'
import { render, screen, act } from '@testing-library/react'
import '@testing-library/jest-dom'
import { TextArea } from './index'
// Mock the styles import
jest.mock('./styles.scss', () => ({}))
describe('@joi/core/TextArea', () => {
@ -31,4 +30,40 @@ describe('@joi/core/TextArea', () => {
const textareaElement = screen.getByTestId('custom-textarea')
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 './styles.scss'
import { ScrollArea } from '../ScrollArea'
type ResizeProps = {
autoResize?: boolean
minResize?: number
maxResize?: number
}
export interface TextAreaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
extends ResizeProps,
React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
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 (
<div className="textarea__wrapper">
<textarea
className={twMerge('textarea', className)}
ref={ref}
ref={autoResize ? textareaRef : ref}
{...props}
/>
</div>

View File

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

View File

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