Skip to content

Commit 3818c72

Browse files
committed
feat(input): adds limited support for invalid and valid pesudo-class
Adds the correct styling for invalid and valid pesudo props. This using the :has selector in places which has limitted support. fix #269
1 parent d60572d commit 3818c72

File tree

3 files changed

+108
-1
lines changed

3 files changed

+108
-1
lines changed

src/components/FormControl/FormControl.stories.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const API_1: Story = () => {
4141
const [disabled, setDisabled] = useState(false)
4242
const [error, setError] = useState(false)
4343
const [valid, setValid] = useState(false)
44+
const [native, setNative] = useState(false)
4445

4546
const rotate = rotateCheckedState(setRequired)
4647

@@ -67,6 +68,11 @@ export const API_1: Story = () => {
6768
onCheckedChange={setValid}
6869
label="Valid"
6970
></Checkbox>
71+
<Checkbox
72+
checked={native}
73+
onCheckedChange={setNative}
74+
label="Native"
75+
></Checkbox>
7076
</Row>
7177

7278
<FormControl>
@@ -77,6 +83,7 @@ export const API_1: Story = () => {
7783
error={error}
7884
valid={valid}
7985
disabled={disabled}
86+
native={native}
8087
required={required === 'indeterminate' ? undefined : required}
8188
/>
8289
<FormControlHelp
@@ -199,11 +206,55 @@ export const Example: Story = () => {
199206
)
200207
}
201208

209+
/**
210+
* The `native` prop can be added to allow the use of the native html `:invalid` and `:valid` pseudo-classes.
211+
*
212+
* This is not the default as can have negative user experience when required forms show initial error.
213+
*
214+
* This is useful for styling the inputs when using the native html validation attributes.
215+
* The is added for checkbox, radio, and select inputs too but uses the :has selector which has limited support.
216+
*/
217+
export const Native: Story = () => {
218+
return (
219+
<Form onSubmit={withFormData(alert)}>
220+
<FormControl>
221+
<Input
222+
name="email"
223+
label="Email"
224+
type="email"
225+
placeholder="hello@committed.io"
226+
minLength={3}
227+
required
228+
native
229+
/>
230+
<FormControlHelp defaultText="Input your email address" />
231+
</FormControl>
232+
<FormControl>
233+
<Input
234+
name="password"
235+
type="password"
236+
label="Password"
237+
pattern="^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$"
238+
required
239+
native
240+
title="Password must have minimum eight characters, at least one letter, one number and one special character"
241+
/>
242+
<FormControlHelp defaultText="Input your password" />
243+
</FormControl>
244+
<FormButton />
245+
</Form>
246+
)
247+
}
248+
249+
/**
250+
* Behavioural test/demo. Use the checkboxes to add these properties to all form elements.
251+
*/
202252
export const Controls: Story = () => {
203253
const [required, setRequired] = useState<CheckedState>('indeterminate')
204254
const [disabled, setDisabled] = useState(false)
205255
const [error, setError] = useState(false)
206256
const [valid, setValid] = useState(false)
257+
const [native, setNative] = useState(false)
207258

208259
const rotate = rotateCheckedState(setRequired)
209260

@@ -212,6 +263,7 @@ export const Controls: Story = () => {
212263
valid,
213264
disabled,
214265
required: required === 'indeterminate' ? undefined : required,
266+
native,
215267
}
216268

217269
return (
@@ -237,6 +289,11 @@ export const Controls: Story = () => {
237289
onCheckedChange={setValid}
238290
label="Valid"
239291
></Checkbox>
292+
<Checkbox
293+
checked={native}
294+
onCheckedChange={setNative}
295+
label="Native"
296+
></Checkbox>
240297
</Row>
241298

242299
<Form onSubmit={withFormData(alert)}>
@@ -309,11 +366,15 @@ export const Controls: Story = () => {
309366
)
310367
}
311368

369+
/**
370+
* Behavioural test/demo with ids, to ensure elements are correctly referenced in aria. Use the checkboxes to add these properties to all form elements.
371+
*/
312372
export const WithIds: Story = () => {
313373
const [required, setRequired] = useState<CheckedState>('indeterminate')
314374
const [disabled, setDisabled] = useState(false)
315375
const [error, setError] = useState(false)
316376
const [valid, setValid] = useState(false)
377+
const [native, setNative] = useState(false)
317378

318379
const rotate = rotateCheckedState(setRequired)
319380

@@ -322,6 +383,7 @@ export const WithIds: Story = () => {
322383
valid,
323384
disabled,
324385
required: required === 'indeterminate' ? undefined : required,
386+
native,
325387
}
326388

327389
return (
@@ -347,6 +409,11 @@ export const WithIds: Story = () => {
347409
onCheckedChange={setValid}
348410
label="Valid"
349411
></Checkbox>
412+
<Checkbox
413+
checked={native}
414+
onCheckedChange={setNative}
415+
label="Native"
416+
></Checkbox>
350417
</Row>
351418

352419
<Form onSubmit={withFormData(alert)}>

src/components/Input/inputStyles.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,29 @@ export const inputStyles = css({
117117
$$active: '$colors$success',
118118
},
119119
},
120+
native: {
121+
false: {},
122+
true: {
123+
'&:invalid': {
124+
backgroundColor: '$errorBackground',
125+
$$inactive: '$colors$errorLowlight',
126+
$$active: '$colors$error',
127+
},
128+
'&:valid': {
129+
$$inactive: '$colors$successLowlight',
130+
$$active: '$colors$success',
131+
},
132+
'&:has(+select:invalid)': {
133+
backgroundColor: '$errorBackground',
134+
$$inactive: '$colors$errorLowlight',
135+
$$active: '$colors$error',
136+
},
137+
'&:has(+select:valid)': {
138+
$$inactive: '$colors$successLowlight',
139+
$$active: '$colors$success',
140+
},
141+
},
142+
},
120143
cursor: {
121144
default: {
122145
cursor: 'default',
@@ -223,6 +246,22 @@ export const checkStyles = css({
223246
$$active: '$colors$success',
224247
},
225248
},
249+
native: {
250+
false: {},
251+
true: {
252+
'&:has(+input:invalid)': {
253+
backgroundColor: '$errorBackground',
254+
borderColor: '$colors$error',
255+
$$inactive: '$colors$errorLowlight',
256+
$$active: '$colors$error',
257+
},
258+
'&:has(+input:valid)': {
259+
borderColor: '$colors$success',
260+
$$inactive: '$colors$successLowlight',
261+
$$active: '$colors$success',
262+
},
263+
},
264+
},
226265
destructive: {
227266
true: {
228267
$$main: '$colors$error',

src/docs/examples/form.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export const Form: Story = () => {
6666
id="name"
6767
label="Name"
6868
name="name"
69+
required
6970
onValueChange={(value) => setName(value)}
7071
value={name}
7172
/>
@@ -108,7 +109,7 @@ export const Form: Story = () => {
108109
checked={notifyBrowser}
109110
/>
110111
<Text>Please Specify your role:</Text>
111-
<RadioGroup value={role} onValueChange={(v) => setRole(v)}>
112+
<RadioGroup required value={role} onValueChange={(v) => setRole(v)}>
112113
<Radio value="director" label="Director" />
113114
<Radio value="developer" label="Developer" />
114115
<Radio value="tester" label="Tester" />

0 commit comments

Comments
 (0)