1+ import React , { useState , useEffect , useRef , HTMLAttributes } from 'react' ;
2+ import makeId from '@/utils/id' ;
3+ import { toClassName } from '@/utils/styling' ;
4+
5+ interface M3TextFieldProps extends HTMLAttributes < HTMLElement > {
6+ id ?: string ;
7+ type ?: string ;
8+ value ?: string | number ;
9+ label ?: string ;
10+ placeholder ?: string ;
11+ lazy ?: boolean ;
12+ multiline ?: boolean ;
13+ invalid ?: boolean ;
14+ disabled ?: boolean ;
15+ readonly ?: boolean ;
16+ outlined ?: boolean ;
17+ onUpdateValue ?: ( value : string | number ) => void ;
18+ }
19+
20+ const M3TextField : React . FC < M3TextFieldProps > = ( {
21+ id = makeId ( 'm3-text-field' ) ,
22+ type = 'text' ,
23+ value = '' ,
24+ label = '' ,
25+ placeholder = '' ,
26+ lazy = false ,
27+ multiline = false ,
28+ invalid = false ,
29+ disabled = false ,
30+ readonly = false ,
31+ outlined = false ,
32+ className = '' ,
33+ children,
34+ onUpdateValue,
35+ ...props
36+ } ) => {
37+ const [ focused , setFocused ] = useState ( false ) ;
38+ const inputElement = useRef < HTMLInputElement | HTMLTextAreaElement | null > ( null ) ;
39+
40+ const onInput = ( event : React . ChangeEvent < HTMLInputElement | HTMLTextAreaElement > ) => {
41+ const rawValue = event . target . value ;
42+ if ( ! lazy ) {
43+ onUpdateValue && onUpdateValue ( rawValue ) ;
44+ }
45+ } ;
46+
47+ const onChange = ( event : React . ChangeEvent < HTMLInputElement | HTMLTextAreaElement > ) => {
48+ const rawValue = event . target . value ;
49+ if ( lazy ) {
50+ onUpdateValue && onUpdateValue ( rawValue ) ;
51+ }
52+ } ;
53+
54+ const onFocus = ( ) => {
55+ setFocused ( true ) ;
56+ } ;
57+
58+ const onBlur = ( ) => {
59+ setFocused ( false ) ;
60+ } ;
61+
62+ useEffect ( ( ) => {
63+ const input = inputElement . current ;
64+ if ( input ) {
65+ const valueStr = String ( value ) ;
66+ if ( valueStr . length ) {
67+ input . value = valueStr ;
68+ } else if ( input . value . length ) {
69+ onUpdateValue && onUpdateValue ( input . value ) ;
70+ }
71+ }
72+ } , [ value , onUpdateValue ] ) ;
73+
74+ return (
75+ < div
76+ className = { toClassName ( [ className , {
77+ 'm3-text-field' : true ,
78+ 'm3-text-field_outlined' : outlined ,
79+ 'm3-text-field_focused' : focused ,
80+ 'm3-text-field_invalid' : invalid ,
81+ 'm3-text-field_disabled' : disabled ,
82+ 'm3-text-field_readonly' : readonly ,
83+ } ] ) }
84+ onClick = { ( ) => inputElement . current ?. focus ( ) }
85+ { ...props }
86+ >
87+ { outlined && < div className = "m3-text-field__outline" >
88+ < div className = "m3-text-field__outline-leading" />
89+ < div className = "m3-text-field__outline-notch" >
90+ < label
91+ id = { `${ id } -label` }
92+ htmlFor = { id }
93+ className = "m3-text-field__label"
94+ >
95+ { label }
96+ </ label >
97+ </ div >
98+ < div className = "m3-text-field__outline-trailing" />
99+ </ div > }
100+
101+ { ! outlined && < label
102+ id = { `${ id } -label` }
103+ htmlFor = { id }
104+ className = "m3-text-field__label"
105+ >
106+ { label }
107+ </ label > }
108+
109+ < div className = "m3-text-field__state" >
110+ { children }
111+
112+ { multiline ? (
113+ < textarea
114+ id = { id }
115+ ref = { inputElement as React . RefObject < HTMLInputElement > }
116+ defaultValue = { String ( value ) }
117+ placeholder = { placeholder }
118+ disabled = { disabled }
119+ readOnly = { readonly }
120+ aria-invalid = { invalid ? 'true' : 'false' }
121+ onInput = { onInput }
122+ onChange = { onChange }
123+ onFocus = { onFocus }
124+ onBlur = { onBlur }
125+ />
126+ ) : (
127+ < input
128+ id = { id }
129+ ref = { inputElement as React . RefObject < HTMLInputElement > }
130+ type = { type }
131+ defaultValue = { String ( value ) }
132+ placeholder = { placeholder }
133+ disabled = { disabled }
134+ readOnly = { readonly }
135+ aria-invalid = { invalid ? 'true' : 'false' }
136+ onInput = { onInput }
137+ onChange = { onChange }
138+ onFocus = { onFocus }
139+ onBlur = { onBlur }
140+ />
141+ ) }
142+ </ div >
143+
144+ { ! outlined && < div className = "m3-text-field__underline" /> }
145+ </ div >
146+ ) ;
147+ } ;
148+
149+ export default M3TextField ;
0 commit comments