1
1
import React , { HTMLProps } from "react" ;
2
2
3
+ import { tokenConfig } from "config" ;
4
+ import { Button } from "_app" ;
5
+
3
6
export const Label : React . FC < {
4
7
htmlFor : string ;
5
8
} > = ( props ) => (
6
- < label className = "block text-sm font-medium text-gray-700 " { ...props } >
9
+ < label className = "block text-sm font-normal text-gray-600 " { ...props } >
7
10
{ props . children }
8
11
</ label >
9
12
) ;
10
13
11
- export const Input : React . FC < HTMLProps < HTMLInputElement > > = ( props ) => (
14
+ export const Input : React . FC <
15
+ HTMLProps < HTMLInputElement > & {
16
+ inputRef ?: React . Ref < HTMLInputElement > | null ;
17
+ }
18
+ > = ( { inputRef, ...props } ) => (
12
19
< input
13
20
name = { props . id }
14
- className = { `w-full bg-white rounded border border-gray-300 focus:border-yellow-500 focus:ring-2 focus:ring-yellow-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out ${
21
+ className = { `w-full bg-white border border-gray-300 focus:border-yellow-500 focus:ring-2 focus:ring-yellow-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out ${
15
22
props . disabled ? "bg-gray-50" : ""
16
23
} `}
24
+ ref = { inputRef }
17
25
{ ...props }
18
26
/>
19
27
) ;
@@ -47,7 +55,7 @@ export const TextArea: React.FC<HTMLProps<HTMLTextAreaElement>> = (props) => (
47
55
< textarea
48
56
rows = { 3 }
49
57
name = { props . id }
50
- className = { `w-full bg-white rounded border border-gray-300 focus:border-yellow-500 focus:ring-2 focus:ring-yellow-200 h-32 text-base outline-none text-gray-700 py-1 px-3 resize-none leading-6 transition-colors duration-200 ease-in-out ${
58
+ className = { `w-full bg-white border border-gray-300 focus:border-yellow-500 focus:ring-2 focus:ring-yellow-200 h-32 text-base outline-none text-gray-700 py-1 px-3 resize-none leading-6 transition-colors duration-200 ease-in-out ${
51
59
props . disabled ? "bg-gray-50" : ""
52
60
} `}
53
61
{ ...props }
@@ -99,6 +107,122 @@ export const LabeledSet: React.FC<{
99
107
) ;
100
108
} ;
101
109
110
+ export const ChainAccountInput = (
111
+ props : HTMLProps < HTMLInputElement > & { id : string }
112
+ ) => {
113
+ const validateAccountField = ( e : React . FormEvent < HTMLInputElement > ) => {
114
+ const target = e . target as HTMLInputElement ;
115
+ if ( target . validity . valueMissing ) {
116
+ target . setCustomValidity ( "Enter an account name" ) ;
117
+ } else {
118
+ target . setCustomValidity ( "Invalid account name" ) ;
119
+ }
120
+ } ;
121
+
122
+ const clearErrorMessages = ( e : React . FormEvent < HTMLInputElement > ) => {
123
+ ( e . target as HTMLInputElement ) . setCustomValidity ( "" ) ;
124
+ } ;
125
+
126
+ const onInput = ( e : React . FormEvent < HTMLInputElement > ) => {
127
+ clearErrorMessages ( e ) ;
128
+ props . onInput ?.( e ) ;
129
+ } ;
130
+
131
+ const onInvalid = ( e : React . FormEvent < HTMLInputElement > ) => {
132
+ validateAccountField ( e ) ;
133
+ props . onInvalid ?.( e ) ;
134
+ } ;
135
+
136
+ return (
137
+ < Form . LabeledSet
138
+ label = { `${ tokenConfig . symbol } account name (12 characters)` }
139
+ htmlFor = { props . id }
140
+ >
141
+ < Form . Input
142
+ type = "text"
143
+ maxLength = { 12 }
144
+ pattern = "^[a-z,1-5.]{1,12}$"
145
+ { ...props }
146
+ onInvalid = { onInvalid }
147
+ onInput = { onInput }
148
+ />
149
+ </ Form . LabeledSet >
150
+ ) ;
151
+ } ;
152
+
153
+ export const AssetInput = (
154
+ props : HTMLProps < HTMLInputElement > & {
155
+ label : string ; // required
156
+ id : string ; // required
157
+ inputRef ?: React . RefObject < HTMLInputElement > ;
158
+ }
159
+ ) => {
160
+ const { label, inputRef, ...inputProps } = props ;
161
+
162
+ const amountRef = inputRef ?? React . useRef < HTMLInputElement > ( null ) ;
163
+
164
+ const amountInputPreventChangeOnScroll = (
165
+ e : React . WheelEvent < HTMLInputElement >
166
+ ) => ( e . target as HTMLInputElement ) . blur ( ) ;
167
+
168
+ const validateAmountField = ( e : React . FormEvent < HTMLInputElement > ) => {
169
+ const target = e . target as HTMLInputElement ;
170
+ if ( target . validity . rangeOverflow ) {
171
+ target . setCustomValidity ( "Insufficient funds available" ) ;
172
+ } else {
173
+ target . setCustomValidity ( "Enter a valid amount" ) ;
174
+ }
175
+ } ;
176
+
177
+ const clearErrorMessages = ( e : React . FormEvent < HTMLInputElement > ) => {
178
+ ( e . target as HTMLInputElement ) . setCustomValidity ( "" ) ;
179
+ } ;
180
+
181
+ const setMaxAmount = ( ) => {
182
+ amountRef . current ?. setCustomValidity ( "" ) ;
183
+
184
+ // ensures this works with uncontrolled instances of this input too
185
+ Object . getOwnPropertyDescriptor (
186
+ window . HTMLInputElement . prototype ,
187
+ "value"
188
+ ) ?. set ?. call ?.( amountRef . current , inputProps . max ) ;
189
+ amountRef . current ?. dispatchEvent (
190
+ new Event ( "change" , { bubbles : true } )
191
+ ) ;
192
+ // (adapted from: https://coryrylan.com/blog/trigger-input-updates-with-react-controlled-inputs)
193
+ } ;
194
+
195
+ return (
196
+ < Form . LabeledSet label = { label } htmlFor = { inputProps . id } >
197
+ < div className = "flex space-x-2" >
198
+ < div className = "relative flex-1" >
199
+ < Form . Input
200
+ type = "number"
201
+ inputMode = "decimal"
202
+ min = { 1 / Math . pow ( 10 , tokenConfig . precision ) }
203
+ step = "any"
204
+ onWheel = { amountInputPreventChangeOnScroll }
205
+ onInvalid = { validateAmountField }
206
+ onInput = { clearErrorMessages }
207
+ inputRef = { amountRef }
208
+ { ...inputProps }
209
+ />
210
+ < div className = "absolute top-3 right-2" >
211
+ < p className = "text-sm text-gray-400" >
212
+ { tokenConfig . symbol }
213
+ </ p >
214
+ </ div >
215
+ </ div >
216
+ { inputProps . max ? (
217
+ < Button type = "neutral" onClick = { setMaxAmount } >
218
+ Max
219
+ </ Button >
220
+ ) : null }
221
+ </ div >
222
+ </ Form . LabeledSet >
223
+ ) ;
224
+ } ;
225
+
102
226
export const Form = {
103
227
Label,
104
228
Input,
@@ -107,6 +231,8 @@ export const Form = {
107
231
LabeledSet,
108
232
FileInput,
109
233
Checkbox,
234
+ ChainAccountInput,
235
+ AssetInput,
110
236
} ;
111
237
112
238
export default Form ;
0 commit comments