dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

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 }