Skip to content

Commit 9b99d73

Browse files
author
Luke Bowerman
authored
Rebase on master (#804)
1 parent 1168ccc commit 9b99d73

File tree

35 files changed

+328
-625
lines changed

35 files changed

+328
-625
lines changed

packages/components/src/Form/Fields/Field.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ import { Label } from '../Label/Label'
3434
import { Paragraph } from '../../Text/Paragraph'
3535
import { Text } from '../../Text/Text'
3636
import { ValidationMessage } from '../ValidationMessage'
37-
import { FieldBaseProps, RequiredStar } from './FieldBase'
37+
import { FieldBaseProps } from './FieldBase'
38+
import { RequiredStar } from './RequiredStar'
3839

3940
type ResponsiveSpaceValue = ResponsiveValue<TLengthStyledSystem>
4041

@@ -86,6 +87,7 @@ export const fieldPropKeys = [
8687

8788
export const pickFieldProps = (props: FieldProps) =>
8889
pick(props, [...fieldPropKeys, 'disabled', 'required', 'className'])
90+
8991
export const omitFieldProps = (props: FieldProps) => omit(props, fieldPropKeys)
9092

9193
/**
@@ -94,7 +96,11 @@ export const omitFieldProps = (props: FieldProps) => omit(props, fieldPropKeys)
9496
* feedback about the status of the input values.
9597
*/
9698

97-
const FieldLayout: FunctionComponent<FieldProps> = ({
99+
interface FieldPropsInternal extends FieldProps {
100+
id: string
101+
}
102+
103+
const FieldLayout: FunctionComponent<FieldPropsInternal> = ({
98104
className,
99105
children,
100106
description,
@@ -112,6 +118,10 @@ const FieldLayout: FunctionComponent<FieldProps> = ({
112118
</Paragraph>
113119
)
114120

121+
const fieldValidation = validationMessage && (
122+
<ValidationMessage {...validationMessage} />
123+
)
124+
115125
return (
116126
<div className={className}>
117127
<Label htmlFor={id} fontWeight={labelFontWeight} fontSize={labelFontSize}>
@@ -120,9 +130,9 @@ const FieldLayout: FunctionComponent<FieldProps> = ({
120130
</Label>
121131
{detail && <FieldDetail>{detail}</FieldDetail>}
122132
<InputArea>{children}</InputArea>
123-
<MessageArea>
124-
{validationMessage && <ValidationMessage {...validationMessage} />}
133+
<MessageArea id={`${id}-describedby`}>
125134
{fieldDescription}
135+
{fieldValidation}
126136
</MessageArea>
127137
</div>
128138
)
@@ -137,7 +147,7 @@ FieldDetail.defaultProps = {
137147
const InputArea = styled.div``
138148
const MessageArea = styled.div``
139149

140-
export const Field = styled(FieldLayout)`
150+
export const Field = styled(FieldLayout)<FieldPropsInternal>`
141151
height: fit-content;
142152
width: ${({ width }) => width || 'fit-content'};
143153
align-items: left;

packages/components/src/Form/Fields/FieldBase.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
SOFTWARE.
2424
2525
*/
26-
import React from 'react'
27-
import styled from 'styled-components'
26+
2827
import { FontSizes, FontWeights } from '@looker/design-tokens'
2928
import { ValidationMessageProps } from '../ValidationMessage/ValidationMessage'
3029

@@ -54,12 +53,3 @@ export interface FieldBaseProps {
5453
*/
5554
validationMessage?: ValidationMessageProps
5655
}
57-
58-
export const RequiredStar = styled((props) => (
59-
<span {...props} aria-hidden="true">
60-
{' '}
61-
*
62-
</span>
63-
))`
64-
color: ${(props) => props.theme.colors.palette.red500};
65-
`

packages/components/src/Form/Fields/FieldCheckbox/FieldCheckbox.test.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626

2727
import 'jest-styled-components'
2828
import React from 'react'
29-
import { assertSnapshot, mountWithTheme } from '@looker/components-test-utils'
29+
import {
30+
assertSnapshot,
31+
mountWithTheme,
32+
renderWithTheme,
33+
} from '@looker/components-test-utils'
3034
import { FieldCheckbox } from './FieldCheckbox'
3135

3236
test('A FieldCheckbox', () => {
@@ -50,7 +54,7 @@ test('A required FieldCheckbox', () => {
5054
const wrapper = mountWithTheme(
5155
<FieldCheckbox id="FieldCheckboxID" label="👍" name="thumbsUp" required />
5256
)
53-
expect(wrapper.text()).toMatch(`👍 *`)
57+
expect(wrapper.text()).toMatch(`👍 required`)
5458
})
5559

5660
test('A disabled FieldCheckbox', () => {
@@ -60,14 +64,21 @@ test('A disabled FieldCheckbox', () => {
6064
wrapper.find('input').html().includes('disabled=""')
6165
})
6266

63-
test('A FieldCheckbox with an error validation aligned to the bottom', () => {
64-
const wrapper = mountWithTheme(
67+
test('A FieldCheckbox with error has proper aria setup', () => {
68+
const errorMessage = 'This is an error'
69+
70+
const { container, getByDisplayValue } = renderWithTheme(
6571
<FieldCheckbox
66-
id="FieldCheckboxID"
67-
label="👍"
68-
name="thumbsUp"
69-
validationMessage={{ message: 'This is an error', type: 'error' }}
72+
id="test"
73+
defaultValue="example"
74+
validationMessage={{ message: errorMessage, type: 'error' }}
7075
/>
7176
)
72-
expect(wrapper.text()).toMatch(`👍This is an error`)
77+
78+
const input = getByDisplayValue('example')
79+
const id = input.getAttribute('aria-describedby')
80+
expect(id).toBeDefined()
81+
82+
const describedBy = container.querySelector(`#${id}`)
83+
expect(describedBy).toHaveTextContent(errorMessage)
7384
})

packages/components/src/Form/Fields/FieldCheckbox/FieldCheckbox.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ const FieldCheckboxLayout = forwardRef(
4141
const { id = uuid() } = props
4242
return (
4343
<FieldInline
44-
validationMessage={validationMessage}
4544
{...pickFieldProps(props)}
45+
validationMessage={validationMessage}
46+
id={id}
4647
>
4748
<Checkbox
4849
{...omitFieldProps(props)}
50+
aria-describedby={`${id}-describedby`}
4951
id={id}
5052
validationType={validationMessage && validationMessage.type}
5153
ref={ref}

packages/components/src/Form/Fields/FieldCheckbox/__snapshots__/FieldCheckbox.test.tsx.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ exports[`A FieldCheckbox 1`] = `
116116
className="c4 "
117117
>
118118
<input
119+
aria-describedby="FieldCheckboxID-describedby"
119120
checked={false}
120121
id="FieldCheckboxID"
121122
name="thumbsUp"
@@ -146,6 +147,7 @@ exports[`A FieldCheckbox 1`] = `
146147
</div>
147148
<div
148149
className="c7 "
150+
id="FieldCheckboxID-describedby"
149151
/>
150152
</label>
151153
`;
@@ -266,6 +268,7 @@ exports[`A FieldCheckbox with checked value 1`] = `
266268
className="c4 "
267269
>
268270
<input
271+
aria-describedby="FieldCheckboxID-describedby"
269272
checked={true}
270273
id="FieldCheckboxID"
271274
name="thumbsUp"
@@ -296,6 +299,7 @@ exports[`A FieldCheckbox with checked value 1`] = `
296299
</div>
297300
<div
298301
className="c7 "
302+
id="FieldCheckboxID-describedby"
299303
/>
300304
</label>
301305
`;

packages/components/src/Form/Fields/FieldColor/FieldColor.test.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,40 @@ describe('FieldColor', () => {
7272
expect(queryByText('Error!')).toBeInTheDocument()
7373
})
7474

75+
test('A FieldColor with description has proper aria setup', () => {
76+
const description = 'This is a description'
77+
78+
const { container, getByDisplayValue } = renderWithTheme(
79+
<FieldColor id="test" defaultValue="example" description={description} />
80+
)
81+
82+
const input = getByDisplayValue('example')
83+
const id = input.getAttribute('aria-describedby')
84+
expect(id).toBeDefined()
85+
86+
const describedBy = container.querySelector(`#${id}`)
87+
expect(describedBy).toHaveTextContent(description)
88+
})
89+
90+
test('A FieldColor with error has proper aria setup', () => {
91+
const errorMessage = 'This is an error'
92+
93+
const { container, getByDisplayValue } = renderWithTheme(
94+
<FieldColor
95+
id="test"
96+
defaultValue="example"
97+
validationMessage={{ message: errorMessage, type: 'error' }}
98+
/>
99+
)
100+
101+
const input = getByDisplayValue('example')
102+
const id = input.getAttribute('aria-describedby')
103+
expect(id).toBeDefined()
104+
105+
const describedBy = container.querySelector(`#${id}`)
106+
expect(describedBy).toHaveTextContent(errorMessage)
107+
})
108+
75109
test('with an onChange', () => {
76110
const onChangeMock = jest.fn()
77111
const { getByLabelText } = renderWithTheme(

packages/components/src/Form/Fields/FieldColor/FieldColor.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,9 @@ export const FieldColorComponent = forwardRef(
182182

183183
return (
184184
<Field
185+
{...pickFieldProps(props)}
185186
id={inputID}
186187
validationMessage={validationMessage}
187-
{...pickFieldProps(props)}
188188
>
189189
<FormControl alignLabel="left">
190190
<Swatch
@@ -202,6 +202,7 @@ export const FieldColorComponent = forwardRef(
202202
{!hideInput && (
203203
<InputText
204204
{...omitFieldProps(props)}
205+
aria-describedby={`${id}-describedby`}
205206
id={inputID}
206207
ref={ref}
207208
borderRadius="none"

packages/components/src/Form/Fields/FieldInline.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,28 @@ import React, { FC } from 'react'
2828
import styled from 'styled-components'
2929
import { Label } from '../Label/Label'
3030
import { ValidationMessage } from '../ValidationMessage/ValidationMessage'
31-
import { FieldBaseProps, RequiredStar } from './FieldBase'
31+
import { FieldBaseProps } from './FieldBase'
32+
import { RequiredStar } from './RequiredStar'
3233

3334
/**
3435
* `<FieldInline />` allows the rendering of a label (for FieldCheckbox, FieldRadio and FieldToggleSwitch),
3536
* and can render a validation message.
3637
* The label will always be placed on the right side of the input.
3738
*/
3839

39-
const FieldInlineLayout: FC<Omit<FieldBaseProps, 'labelFontWeight'>> = ({
40+
interface FieldInlinePropsInternal extends FieldBaseProps {
41+
id: string
42+
}
43+
44+
const FieldInlineLayout: FC<Omit<
45+
FieldInlinePropsInternal,
46+
'labelFontWeight'
47+
>> = ({
4048
className,
4149
children,
4250
label,
4351
labelFontSize,
52+
id,
4453
required,
4554
validationMessage,
4655
}) => {
@@ -51,7 +60,7 @@ const FieldInlineLayout: FC<Omit<FieldBaseProps, 'labelFontWeight'>> = ({
5160
{required && <RequiredStar />}
5261
</Label>
5362
<InputArea>{children}</InputArea>
54-
<MessageArea>
63+
<MessageArea id={`${id}-describedby`}>
5564
{validationMessage ? (
5665
<ValidationMessage {...validationMessage} />
5766
) : null}

packages/components/src/Form/Fields/FieldRadio/FieldRadio.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,13 @@ const FieldRadioLayout = forwardRef(
4040
(props: FieldRadioProps, ref: Ref<HTMLInputElement>) => {
4141
const { id = uuid() } = props
4242
return (
43-
<FieldInline {...pickFieldProps(props)}>
44-
<Radio {...omitFieldProps(props)} id={id} ref={ref} />
43+
<FieldInline {...pickFieldProps(props)} id={id}>
44+
<Radio
45+
{...omitFieldProps(props)}
46+
aria-describedby={`${id}-describedby`}
47+
id={id}
48+
ref={ref}
49+
/>
4550
</FieldInline>
4651
)
4752
}

packages/components/src/Form/Fields/FieldRadio/__snapshots__/FieldRadio.test.tsx.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ exports[`A FieldRadio 1`] = `
121121
className="c4 "
122122
>
123123
<input
124+
aria-describedby="FieldRadioID-describedby"
124125
checked={false}
125126
id="FieldRadioID"
126127
name="thumbsUp"
@@ -136,6 +137,7 @@ exports[`A FieldRadio 1`] = `
136137
</div>
137138
<div
138139
className="c7 "
140+
id="FieldRadioID-describedby"
139141
/>
140142
</label>
141143
`;
@@ -261,6 +263,7 @@ exports[`A FieldRadio checked 1`] = `
261263
className="c4 "
262264
>
263265
<input
266+
aria-describedby="FieldRadioID-describedby"
264267
checked={false}
265268
id="FieldRadioID"
266269
name="thumbsUp"
@@ -276,6 +279,7 @@ exports[`A FieldRadio checked 1`] = `
276279
</div>
277280
<div
278281
className="c7 "
282+
id="FieldRadioID-describedby"
279283
/>
280284
</label>
281285
`;

0 commit comments

Comments
 (0)