5
5
*
6
6
* Copyright Oxide Computer Company
7
7
*/
8
+ import cn from 'classnames'
8
9
import { useId } from 'react'
9
10
import {
10
11
useController ,
@@ -30,6 +31,7 @@ export interface TextFieldProps<
30
31
TFieldValues extends FieldValues ,
31
32
TName extends FieldPath < TFieldValues > ,
32
33
> extends UITextFieldProps {
34
+ variant ?: 'default' | 'inline'
33
35
name : TName
34
36
/** HTML type attribute, defaults to text */
35
37
type ?: 'text' | 'password'
@@ -54,18 +56,27 @@ export function TextField<
54
56
TFieldValues extends FieldValues ,
55
57
TName extends FieldPath < TFieldValues > ,
56
58
> ( {
59
+ variant = 'default' ,
57
60
name,
61
+ type = 'text' ,
58
62
label = capitalize ( name ) ,
59
63
units,
60
64
description,
61
65
required,
66
+ control,
67
+ validate,
68
+ transform,
62
69
...props
63
70
} : Omit < TextFieldProps < TFieldValues , TName > , 'id' > & UITextAreaProps ) {
64
- // id is omitted from props because we generate it here
65
71
const id = useId ( )
72
+ const {
73
+ field : { onChange, ...fieldRest } ,
74
+ fieldState : { error } ,
75
+ } = useController ( { name, control, rules : { required, validate } } )
66
76
return (
67
- < div className = "max-w-lg" >
68
- < div className = "mb-2" >
77
+ < div className = { cn ( variant !== 'inline' && 'max-w-lg' ) } >
78
+ { /* Hiding the label for inline inputs but keeping it available for screen readers */ }
79
+ < div className = { cn ( 'mb-2' , variant === 'inline' && 'sr-only' ) } >
69
80
< FieldLabel htmlFor = { id } id = { `${ id } -label` } optional = { ! required } >
70
81
{ label } { units && < span className = "ml-1 text-default" > ({ units } )</ span > }
71
82
</ FieldLabel >
@@ -75,54 +86,18 @@ export function TextField<
75
86
</ TextInputHint >
76
87
) }
77
88
</ div >
78
- { /* passing the generated id is very important for a11y */ }
79
- < TextFieldInner name = { name } { ...props } id = { id } />
80
- </ div >
81
- )
82
- }
83
-
84
- /**
85
- * Primarily exists for `TextField`, but we occasionally also need a plain field
86
- * without a label on it.
87
- *
88
- * Note that `id` is an allowed prop, unlike in `TextField`, where it is always
89
- * generated from `name`. This is because we need to pass the generated ID in
90
- * from there to here. For the case where `TextFieldInner` is used
91
- * independently, we also generate an ID for use only if none is passed in.
92
- */
93
- export const TextFieldInner = <
94
- TFieldValues extends FieldValues ,
95
- TName extends FieldPath < TFieldValues > ,
96
- > ( {
97
- name,
98
- type = 'text' ,
99
- label = capitalize ( name ) ,
100
- validate,
101
- control,
102
- required,
103
- id : idProp ,
104
- transform,
105
- ...props
106
- } : TextFieldProps < TFieldValues , TName > & UITextAreaProps ) => {
107
- const generatedId = useId ( )
108
- const id = idProp || generatedId
109
- const {
110
- field : { onChange, ...fieldRest } ,
111
- fieldState : { error } ,
112
- } = useController ( { name, control, rules : { required, validate } } )
113
- return (
114
- < >
115
89
< UITextField
116
90
id = { id }
117
91
title = { label }
118
92
type = { type }
119
93
error = { ! ! error }
120
- aria-labelledby = { `${ id } -label ${ id } -help-text` }
94
+ aria-labelledby = { cn ( `${ id } -label` , description ? ` ${ id } -help-text` : '' ) }
121
95
onChange = { ( e ) => onChange ( transform ? transform ( e . target . value ) : e . target . value ) }
122
96
{ ...fieldRest }
123
97
{ ...props }
124
98
/>
99
+ { /* todo: inline error message tooltip */ }
125
100
< ErrorMessage error = { error } label = { label } />
126
- </ >
101
+ </ div >
127
102
)
128
103
}
0 commit comments