Skip to content

Commit c72ca7d

Browse files
committed
Merge branch 'master' of github.com:NHSDigital/nhsuk-react-components
2 parents a6e3fba + 7768002 commit c72ca7d

File tree

7 files changed

+241
-12
lines changed

7 files changed

+241
-12
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nhsuk-react-components",
3-
"version": "1.1.2",
3+
"version": "1.1.3",
44
"author": {
55
"email": "[email protected]",
66
"name": "Thomas Judd-Cooper",

src/components/checkboxes/Checkboxes.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ class Checkboxes extends PureComponent<CheckboxesProps, CheckboxesState> {
8080
hintProps={hintProps}
8181
/>
8282
<CheckboxContext.Provider value={{ isCheckbox: true, name, getBoxId: this.getBoxId }}>
83-
<div className={classNames('nhsuk-checkboxes', className)} id={id} {...rest}>
83+
<div
84+
className={classNames('nhsuk-checkboxes', className)}
85+
id={id}
86+
aria-describedby={hint ? `${id}--hint` : undefined}
87+
{...rest}
88+
>
8489
{children}
8590
</div>
8691
</CheckboxContext.Provider>

src/components/date-input/DateInput.tsx

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import classNames from 'classnames';
1111
import { FormElementProps } from '../../util/types/FormTypes';
1212
import { generateRandomName } from '../../util/RandomName';
1313
import LabelBlock from '../../util/LabelBlock';
14+
import FormContext, { IFormContext } from '../form/FormContext';
1415

1516
interface IDateInputContext {
1617
isDateInput: boolean;
1718
registerRef: (type: DateInputType, ref: HTMLInputElement | null) => void;
19+
registerError: (type: DateInputType, error: boolean | undefined) => void;
1820
name: string;
1921
autoCompletePrefix: string | undefined;
2022
error?: string | boolean;
@@ -23,6 +25,7 @@ interface IDateInputContext {
2325
const DateInputContext = createContext<IDateInputContext>({
2426
isDateInput: false,
2527
registerRef: () => undefined,
28+
registerError: () => undefined,
2629
name: '',
2730
autoCompletePrefix: '',
2831
error: '',
@@ -41,7 +44,7 @@ const DateInputInput: React.FC<DateInputInputProps> = ({
4144
autoComplete,
4245
...rest
4346
}) => {
44-
const { isDateInput, registerRef, name, autoCompletePrefix, error } = useContext<
47+
const { isDateInput, registerRef, name, autoCompletePrefix, registerError, error } = useContext<
4548
IDateInputContext
4649
>(DateInputContext);
4750
const inputRef = useRef<HTMLInputElement>(null);
@@ -52,6 +55,12 @@ const DateInputInput: React.FC<DateInputInputProps> = ({
5255
}
5356
}, [inputRef.current]);
5457

58+
useEffect(() => {
59+
if (isDateInput) {
60+
registerError(dateInputType, rest.error);
61+
}
62+
}, [rest.error]);
63+
5564
return (
5665
<div className="nhsuk-date-input__item">
5766
<div className="nhsuk-form-group">
@@ -123,6 +132,12 @@ type DateInputValue = {
123132
year: string;
124133
};
125134

135+
type DateInputErrors = {
136+
day: boolean | undefined;
137+
month: boolean | undefined;
138+
year: boolean | undefined;
139+
};
140+
126141
interface DateInputProps
127142
extends Omit<HTMLProps<HTMLDivElement>, 'onChange' | 'value' | 'defaultValue'>,
128143
FormElementProps {
@@ -133,11 +148,16 @@ interface DateInputProps
133148
defaultValue?: DateInputValue;
134149
}
135150

136-
type DateInputState = {
151+
export type DateInputState = {
137152
name: string;
138153
value: DateInputValue;
154+
errors: DateInputErrors;
139155
};
140156

157+
interface DateInput extends PureComponent<DateInputProps, DateInputState> {
158+
context: IFormContext;
159+
}
160+
141161
interface DateInput extends PureComponent<DateInputProps, DateInputState> {
142162
monthRef: HTMLInputElement | null;
143163
yearRef: HTMLInputElement | null;
@@ -149,11 +169,33 @@ class DateInput extends PureComponent<DateInputProps, DateInputState> {
149169
this.state = {
150170
name: props.name || generateRandomName('dateinput'),
151171
value: { day: '', month: '', year: '' },
172+
errors: { day: undefined, month: undefined, year: undefined },
152173
};
153174
this.monthRef = null;
154175
this.yearRef = null;
155176
}
156177

178+
componentDidUpdate() {
179+
if (!this.context.isForm) return;
180+
if (this.props.error !== undefined) {
181+
this.context.setError(this.state.name, Boolean(this.props.error));
182+
} else {
183+
const { day, month, year } = this.state.errors;
184+
const errorInChild = day || month || year;
185+
this.context.setError(this.state.name, Boolean(errorInChild));
186+
}
187+
}
188+
189+
registerError = (type: DateInputType, error: boolean | undefined) => {
190+
this.setState(state => ({
191+
...state,
192+
errors: {
193+
...state.errors,
194+
[type]: error,
195+
},
196+
}));
197+
};
198+
157199
registerRef = (type: DateInputType, ref: HTMLInputElement | null) => {
158200
if (ref !== null) {
159201
if (type === 'Month') {
@@ -166,7 +208,6 @@ class DateInput extends PureComponent<DateInputProps, DateInputState> {
166208

167209
onChange = (e: SyntheticEvent<HTMLInputElement>) => {
168210
e.stopPropagation();
169-
170211
const target = e.target as HTMLInputElement;
171212
const { value, name } = this.state;
172213
if (target && target.name) {
@@ -188,6 +229,7 @@ class DateInput extends PureComponent<DateInputProps, DateInputState> {
188229
currentTarget: { ...target, name, value },
189230
};
190231

232+
this.setState({ value });
191233
if (this.props.onChange) {
192234
this.props.onChange(newEvent);
193235
}
@@ -202,6 +244,8 @@ class DateInput extends PureComponent<DateInputProps, DateInputState> {
202244
}
203245
};
204246

247+
static contextType = FormContext;
248+
205249
static Day = DateInputDay;
206250

207251
static Month = DateInputMonth;
@@ -227,8 +271,10 @@ class DateInput extends PureComponent<DateInputProps, DateInputState> {
227271
...rest
228272
} = this.props;
229273
const { name } = this.state;
274+
230275
const contextValue = {
231276
isDateInput: true,
277+
registerError: this.registerError,
232278
registerRef: this.registerRef,
233279
name,
234280
autoCompletePrefix,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from 'react';
2+
import { mount } from 'enzyme';
3+
import DateInput from '../DateInput';
4+
5+
describe('DateInput', () => {
6+
it('matches snapshot', () => {
7+
const component = mount(<DateInput name="testInput" />);
8+
expect(component).toMatchSnapshot();
9+
component.unmount();
10+
});
11+
12+
it('wraps onChange handlers', () => {
13+
const onChange = jest.fn();
14+
const component = mount(<DateInput name="testInput" onChange={onChange} />);
15+
// Day
16+
component
17+
.find('div.nhsuk-date-input')
18+
.simulate('change', { target: { name: 'testInput-day', value: '27' } });
19+
expect(component.state('value')).toEqual({
20+
day: '27',
21+
month: '',
22+
year: '',
23+
});
24+
expect(onChange).toHaveBeenCalledTimes(1);
25+
expect(onChange.mock.calls[0][0].target.value).toEqual({
26+
day: '27',
27+
month: '',
28+
year: '',
29+
});
30+
31+
// Month
32+
component
33+
.find('div.nhsuk-date-input')
34+
.simulate('change', { target: { name: 'testInput-month', value: '06' } });
35+
expect(component.state('value')).toEqual({
36+
day: '27',
37+
month: '06',
38+
year: '',
39+
});
40+
expect(onChange).toHaveBeenCalledTimes(2);
41+
expect(onChange.mock.calls[0][0].target.value).toEqual({
42+
day: '27',
43+
month: '06',
44+
year: '',
45+
});
46+
47+
// Year
48+
component
49+
.find('div.nhsuk-date-input')
50+
.simulate('change', { target: { name: 'testInput-year', value: '2000' } });
51+
expect(component.state('value')).toEqual({
52+
day: '27',
53+
month: '06',
54+
year: '2000',
55+
});
56+
expect(onChange).toHaveBeenCalledTimes(3);
57+
expect(onChange.mock.calls[0][0].target.value).toEqual({
58+
day: '27',
59+
month: '06',
60+
year: '2000',
61+
});
62+
63+
component.unmount();
64+
});
65+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`DateInput matches snapshot 1`] = `
4+
<DateInput
5+
name="testInput"
6+
>
7+
<LabelBlock />
8+
<div
9+
className="nhsuk-date-input"
10+
name="testInput"
11+
onChange={[Function]}
12+
>
13+
<DateInputDay>
14+
<DateInputInput
15+
dateInputType="Day"
16+
pattern="[0-9]*"
17+
type="number"
18+
>
19+
<div
20+
className="nhsuk-date-input__item"
21+
>
22+
<div
23+
className="nhsuk-form-group"
24+
>
25+
<label
26+
className="nhsuk-label nhsuk-date-input__label"
27+
htmlFor="testInput-day"
28+
>
29+
Day
30+
</label>
31+
<input
32+
aria-label="testInput-day input"
33+
className="nhsuk-input nhsuk-date-input__input nhsuk-input--width-2"
34+
id="testInput-day"
35+
name="testInput-day"
36+
pattern="[0-9]*"
37+
type="number"
38+
/>
39+
</div>
40+
</div>
41+
</DateInputInput>
42+
</DateInputDay>
43+
<DateInputMonth>
44+
<DateInputInput
45+
dateInputType="Month"
46+
pattern="[0-9]*"
47+
type="number"
48+
>
49+
<div
50+
className="nhsuk-date-input__item"
51+
>
52+
<div
53+
className="nhsuk-form-group"
54+
>
55+
<label
56+
className="nhsuk-label nhsuk-date-input__label"
57+
htmlFor="testInput-month"
58+
>
59+
Month
60+
</label>
61+
<input
62+
aria-label="testInput-month input"
63+
className="nhsuk-input nhsuk-date-input__input nhsuk-input--width-2"
64+
id="testInput-month"
65+
name="testInput-month"
66+
pattern="[0-9]*"
67+
type="number"
68+
/>
69+
</div>
70+
</div>
71+
</DateInputInput>
72+
</DateInputMonth>
73+
<DateInputYear>
74+
<DateInputInput
75+
dateInputType="Year"
76+
pattern="[0-9]*"
77+
type="number"
78+
>
79+
<div
80+
className="nhsuk-date-input__item"
81+
>
82+
<div
83+
className="nhsuk-form-group"
84+
>
85+
<label
86+
className="nhsuk-label nhsuk-date-input__label"
87+
htmlFor="testInput-year"
88+
>
89+
Year
90+
</label>
91+
<input
92+
aria-label="testInput-year input"
93+
className="nhsuk-input nhsuk-date-input__input nhsuk-input--width-4"
94+
id="testInput-year"
95+
name="testInput-year"
96+
pattern="[0-9]*"
97+
type="number"
98+
/>
99+
</div>
100+
</div>
101+
</DateInputInput>
102+
</DateInputYear>
103+
</div>
104+
</DateInput>
105+
`;

src/components/header/components/NHSLogo.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import React, { useContext, HTMLProps } from 'react';
1+
import React, { useContext, HTMLProps, SVGProps } from 'react';
22
import classNames from 'classnames';
33
import HeaderContext, { IHeaderContext } from '../HeaderContext';
44

5+
interface SVGImageWithSrc extends SVGProps<SVGImageElement> {
6+
src: string;
7+
}
8+
9+
const SVGImageWithSrc: React.FC<SVGImageWithSrc> = props => <image {...props} />;
10+
511
const NHSLogo: React.FC<HTMLProps<HTMLAnchorElement>> = ({ className, alt, ...rest }) => {
612
const { serviceName, hasMenuToggle, hasSearch } = useContext<IHeaderContext>(HeaderContext);
713
return (
@@ -21,18 +27,18 @@ const NHSLogo: React.FC<HTMLProps<HTMLAnchorElement>> = ({ className, alt, ...re
2127
<svg
2228
className="nhsuk-logo"
2329
xmlns="http://www.w3.org/2000/svg"
24-
role="presentation"
30+
role="img"
2531
focusable="false"
2632
viewBox="0 0 40 16"
2733
aria-labelledby="nhsuk-logo_title"
2834
>
2935
<title id="nhsuk-logo_title">{alt}</title>
30-
<path className="nhsuk-logo__background" d="M0 0h40v16H0z"></path>
36+
<path className="nhsuk-logo__background" d="M0 0h40v16H0z" />
3137
<path
3238
className="nhsuk-logo__text"
3339
d="M3.9 1.5h4.4l2.6 9h.1l1.8-9h3.3l-2.8 13H9l-2.7-9h-.1l-1.8 9H1.1M17.3 1.5h3.6l-1 4.9h4L25 1.5h3.5l-2.7 13h-3.5l1.1-5.6h-4.1l-1.2 5.6h-3.4M37.7 4.4c-.7-.3-1.6-.6-2.9-.6-1.4 0-2.5.2-2.5 1.3 0 1.8 5.1 1.2 5.1 5.1 0 3.6-3.3 4.5-6.4 4.5-1.3 0-2.9-.3-4-.7l.8-2.7c.7.4 2.1.7 3.2.7s2.8-.2 2.8-1.5c0-2.1-5.1-1.3-5.1-5 0-3.4 2.9-4.4 5.8-4.4 1.6 0 3.1.2 4 .6"
34-
></path>
35-
<img src="https://assets.nhs.uk/images/nhs-logo.png" alt={alt}/>
40+
/>
41+
<SVGImageWithSrc src="https://assets.nhs.uk/images/nhs-logo.png" xlinkHref="" />
3642
</svg>
3743
{serviceName ? <span className="nhsuk-header__service-name">{serviceName}</span> : null}
3844
</a>
@@ -42,7 +48,7 @@ const NHSLogo: React.FC<HTMLProps<HTMLAnchorElement>> = ({ className, alt, ...re
4248

4349
NHSLogo.defaultProps = {
4450
'aria-label': 'NHS homepage',
45-
'alt': 'NHS Logo',
51+
alt: 'NHS Logo',
4652
};
4753

4854
export default NHSLogo;

src/components/header/components/OrganisationalLogo.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ const OrganisationalLogo: React.FC<OrganisationalLogoProps> = ({ logoUrl, alt, .
1717
<svg
1818
className="nhsuk-logo"
1919
xmlns="http://www.w3.org/2000/svg"
20-
role="presentation"
20+
role="img"
2121
focusable="false"
2222
viewBox="0 0 40 16"
23+
aria-labelledby="nhsuk-logo_title"
2324
>
25+
<title id="nhsuk-logo_title">{alt}</title>
2426
<path className="nhsuk-logo__background" d="M0 0h40v16H0z"></path>
2527
<path
2628
className="nhsuk-logo__text"

0 commit comments

Comments
 (0)