Field.tsx (2677B)
1 import classNames from '@/lib/classnames' 2 import { createRef, FormEvent, HTMLAttributes, useEffect, useMemo } from 'react' 3 import styles from './Field.module.css' 4 5 export interface FieldProps extends HTMLAttributes<HTMLLabelElement> { 6 color?: 'primary' | 'secondary' | 'tertiary' | 'error' 7 disabled?: boolean 8 message?: string 9 readOnly?: boolean 10 type: 'date' | 'datetime-local' | 'email' | 'month' | 'number' 11 | 'password' | 'search' | 'tel' | 'time' | 'url' | 'week' | 'text' 12 | 'textarea' 13 value?: string 14 15 onValueChange?: (value: string) => void 16 } 17 18 // Field is a wrapper around an input element that provides a label and 19 // error message. 20 // Parent of this component is required to have background-color. 21 export default function Field (props: FieldProps) { 22 const { 23 color, 24 className: className, 25 disabled, 26 message, 27 placeholder, 28 readOnly, 29 type, 30 value, 31 onValueChange, 32 ...restProps 33 } = props 34 35 const handleInput = (e: FormEvent<HTMLInputElement | HTMLTextAreaElement>) => { 36 onValueChange?.(e.currentTarget.value) 37 } 38 39 const textareaRef = createRef<HTMLTextAreaElement>() 40 41 useEffect(() => { 42 if (type !== 'textarea') { 43 return 44 } 45 46 const textarea = textareaRef.current 47 if (textarea === null) { 48 return 49 } 50 51 textarea.style.height = '0px' 52 const scrollHeight = textarea.scrollHeight 53 textarea.style.height = `${scrollHeight}px` 54 }, [type, value]) 55 56 return ( 57 <> 58 <label 59 {...classNames( 60 className, 61 styles['field'], 62 (value?.length ?? 0) > 0 ? styles['has-content'] : null, 63 color == null ? styles[`has-no-color`] : undefined, 64 color != null ? styles[`is-${color}`] : undefined, 65 readOnly !== true ? styles['is-not-readonly'] : null, 66 disabled === true ? styles['is-disabled'] : null, 67 )} 68 {...restProps} 69 > 70 {type === 'textarea' ? ( 71 <textarea 72 className={styles['textarea']} 73 disabled={disabled} 74 readOnly={readOnly} 75 value={value} 76 onInput={handleInput} 77 ref={textareaRef} 78 /> 79 ) : ( 80 <input 81 className={styles['input']} 82 type={type} 83 disabled={disabled} 84 readOnly={readOnly} 85 value={value} 86 onInput={handleInput} 87 /> 88 )} 89 <span className={styles['label']}>{placeholder}</span> 90 <div className={styles['label-background']}>{placeholder}</div> 91 </label> 92 93 {message != null ? ( 94 <p className={styles['message']}>{message}</p> 95 ) : null} 96 </> 97 ) 98 }