Repository: govuk-react/govuk-react (ts)

Issue:
DateField Input should allow an id to be specified for each field
DateField Input should allow an id to be specified for each field

This is for compatibility with ErrorSummary guidance:

> When a user has to enter their answer into multiple fields, such as the day, month and year fields in the date input component, link to the first field that contains an error.
> 
> If you do not know which field contains an error, link to the first field.

https://design-system.service.gov.uk/components/error-summary/

Pull request:
Date - per field props (e.g. autocomplete and id)
Fixes #926

**Checklist**:

* [x] Documentation
* [x] Tests
* [x] Ready to be merged

## Screenshots

![image](https://user-images.githubusercontent.com/4953590/166842219-16f2c724-94e0-408e-9490-b2fcf1f13dd2.png)

![image](https://user-images.githubusercontent.com/4953590/166842236-c880ae16-57dc-4f4f-a73d-8ee7b992d001.png)

![image](https://user-images.githubusercontent.com/4953590/166842275-576007a0-7ee7-44ae-aced-fb4cad402bb8.png)

## Also see

- https://design-system.service.gov.uk/components/date-input/#use-the-autocomplete-attribute-for-a-date-of-birth
- https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
- https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html

Patch:
diff --git a/API.md b/API.md
index 8aad34cb..4de26fd3 100644
--- a/API.md
+++ b/API.md
@@ -1540,6 +1540,7 @@ Prop | Required | Default | Type | Description
  `input` |  | ```undefined``` | { onChange?: (date: { day: string; month: string; year: string; }) => unknown; onBlur?: (date: { day: string; month: string; year: string; }) => void; onFocus?: (date: { day: string; month: string; year: string; }) => void; value?: { ...; }; } | Properties that are sent to the input, matching final form and redux form input type
  `inputMode` |  |  | "text" \| "search" \| "none" \| "tel" \| "url" \| "email" \| "numeric" \| "decimal" | Hints at the type of data that might be entered by the user while editing the element or its contents<br/>@see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute
  `inputNames` |  | ```{     day: undefined,     month: undefined,     year: undefined,   }``` | { day?: string; month?: string; year?: string; } | Input name attributes
+ `inputs` |  | ```{     day: undefined,     month: undefined,     year: undefined,   }``` | { day?: InputProps; month?: InputProps; year?: InputProps; } | Custom props to pass down to the input fields
  `is` |  |  | string | Specify that a standard HTML element should behave like a defined custom built-in element<br/>@see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is
  `itemID` |  |  | string | 
  `itemProp` |  |  | string | 
diff --git a/components/date-field/README.md b/components/date-field/README.md
index cf43f0d0..560a9757 100644
--- a/components/date-field/README.md
+++ b/components/date-field/README.md
@@ -87,6 +87,7 @@ Prop | Required | Default | Type | Description
  `input` |  | ```undefined``` | { onChange?: (date: { day: string; month: string; year: string; }) => unknown; onBlur?: (date: { day: string; month: string; year: string; }) => void; onFocus?: (date: { day: string; month: string; year: string; }) => void; value?: { ...; }; } | Properties that are sent to the input, matching final form and redux form input type
  `inputMode` |  |  | "text" \| "search" \| "none" \| "tel" \| "url" \| "email" \| "numeric" \| "decimal" | Hints at the type of data that might be entered by the user while editing the element or its contents<br/>@see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute
  `inputNames` |  | ```{     day: undefined,     month: undefined,     year: undefined,   }``` | { day?: string; month?: string; year?: string; } | Input name attributes
+ `inputs` |  | ```{     day: undefined,     month: undefined,     year: undefined,   }``` | { day?: InputProps; month?: InputProps; year?: InputProps; } | Custom props to pass down to the input fields
  `is` |  |  | string | Specify that a standard HTML element should behave like a defined custom built-in element<br/>@see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is
  `itemID` |  |  | string | 
  `itemProp` |  |  | string | 
diff --git a/components/date-field/src/index.tsx b/components/date-field/src/index.tsx
index 9c346a1e..4f72adf0 100644
--- a/components/date-field/src/index.tsx
+++ b/components/date-field/src/index.tsx
@@ -14,6 +14,7 @@ import LabelText from '@govuk-react/label-text';
 import ErrorText from '@govuk-react/error-text';
 import HintText from '@govuk-react/hint-text';
 import { spacing } from '@govuk-react/lib';
+import { InputProps as BaseInputProps } from '@govuk-react/input';
 
 import Input from './input';
 
@@ -45,6 +46,7 @@ export const DateField: DateFieldType = ({
   hintText,
   inputNames,
   defaultValues,
+  inputs,
   input,
   ...props
 }: DateFieldProps) => (
@@ -52,7 +54,7 @@ export const DateField: DateFieldType = ({
     <LabelText>{children}</LabelText>
     {hintText && <HintText>{hintText}</HintText>}
     {errorText && <ErrorText>{errorText}</ErrorText>}
-    <Input names={inputNames} defaultValues={defaultValues} error={!!errorText} {...input} />
+    <Input names={inputNames} defaultValues={defaultValues} error={!!errorText} inputs={inputs} {...input} />
   </StyledContainer>
 );
 DateField.displayName = 'DateField';
@@ -78,6 +80,11 @@ DateField.defaultProps = {
     month: undefined,
     year: undefined,
   },
+  inputs: {
+    day: undefined,
+    month: undefined,
+    year: undefined,
+  },
   input: undefined,
 };
 
@@ -104,6 +111,14 @@ export interface DateFieldProps extends React.HTMLAttributes<HTMLDivElement>, Wi
     month?: string;
     year?: string;
   };
+  /**
+   * Custom props to pass down to the input fields
+   */
+  inputs?: {
+    day?: BaseInputProps;
+    month?: BaseInputProps;
+    year?: BaseInputProps;
+  };
   /**
    * Properties that are sent to the input, matching final form and redux form input type
    */
diff --git a/components/date-field/src/input.tsx b/components/date-field/src/input.tsx
index 4862a934..7d19c1a4 100644
--- a/components/date-field/src/input.tsx
+++ b/components/date-field/src/input.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import styled from 'styled-components';
 
-import BaseInput from '@govuk-react/input';
+import BaseInput, { InputProps as BaseInputProps } from '@govuk-react/input';
 import LabelText from '@govuk-react/label-text';
 import Label from '@govuk-react/label';
 
@@ -21,8 +21,8 @@ const StyledList = styled('div')({
   display: 'flex',
 });
 
-class Input extends React.Component<InputProps> {
-  static defaultProps = {
+class Input extends React.Component<DateInputProps> {
+  static defaultProps: Partial<DateInputProps> = {
     value: undefined,
     names: {
       day: 'DateFieldDay',
@@ -34,6 +34,11 @@ class Input extends React.Component<InputProps> {
       month: undefined,
       year: undefined,
     },
+    inputs: {
+      day: undefined,
+      month: undefined,
+      year: undefined,
+    },
     labels: {
       day: 'Day',
       month: 'Month',
@@ -48,7 +53,7 @@ class Input extends React.Component<InputProps> {
 
   inputs = {};
 
-  renderInput(label, name, key, defaultValue, error) {
+  renderInput(label, name, key, defaultValue, input, error) {
     const { value, onChange, onBlur, onFocus, refs } = this.props;
     return (
       <StyledLabel year={key === 'year'}>
@@ -61,28 +66,29 @@ class Input extends React.Component<InputProps> {
           onChange={(e) => onChange(e, key)}
           onBlur={(e) => onBlur(e, key)}
           onFocus={(e) => onFocus(e, key)}
-          ref={(input) => {
-            this.inputs[key] = input;
+          ref={(inputElement) => {
+            this.inputs[key] = inputElement;
             refs(this.inputs);
           }}
+          {...input}
         />
       </StyledLabel>
     );
   }
 
   render() {
-    const { labels, names, defaultValues, error } = this.props;
+    const { labels, names, defaultValues, inputs, error } = this.props;
     return (
       <StyledList>
-        {this.renderInput(labels.day, names.day, 'day', defaultValues.day, error)}
-        {this.renderInput(labels.month, names.month, 'month', defaultValues.month, error)}
-        {this.renderInput(labels.year, names.year, 'year', defaultValues.year, error)}
+        {this.renderInput(labels.day, names.day, 'day', defaultValues.day, inputs.day, error)}
+        {this.renderInput(labels.month, names.month, 'month', defaultValues.month, inputs.month, error)}
+        {this.renderInput(labels.year, names.year, 'year', defaultValues.year, inputs.year, error)}
       </StyledList>
     );
   }
 }
 
-export interface InputProps {
+export interface DateInputProps {
   names?: {
     day?: string;
     month?: string;
@@ -93,6 +99,14 @@ export interface InputProps {
     month?: string;
     year?: string;
   };
+  /**
+   * Custom props to pass down to the input fields
+   */
+  inputs?: {
+    day?: BaseInputProps;
+    month?: BaseInputProps;
+    year?: BaseInputProps;
+  };
   value?: {
     day?: string;
     month?: string;
diff --git a/components/date-field/src/stories.tsx b/components/date-field/src/stories.tsx
index b0fc0880..5f1dadd7 100644
--- a/components/date-field/src/stories.tsx
+++ b/components/date-field/src/stories.tsx
@@ -21,6 +21,11 @@ Default.args = {
   },
   inputNames: { day: 'dayInputName' },
   children: 'What is your date of birth?',
+  inputs: {
+    day: { autoComplete: 'bday-day' },
+    month: { autoComplete: 'bday-month' },
+    year: { autoComplete: 'bday-year' },
+  },
 };
 
 export const DateWithHintText = Template.bind({});
@@ -41,3 +46,21 @@ DateWithHintTextError.args = {
   children: 'What is your date of birth?',
   errorText: 'Error message goes here',
 };
+
+export const DateWithPerFieldProps = Template.bind({});
+DateWithPerFieldProps.parameters = {
+  docs: {
+    description: {
+      story:
+        'Use the relevant autocomplete parameters to ensure your service is compliant with [WCAG 2.1 Level AA success criterion 1.3.5]((https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html).',
+    },
+  },
+};
+DateWithPerFieldProps.args = {
+  children: 'What is your date of birth?',
+  inputs: {
+    day: { autoComplete: 'bday-day', id: 'birthday-day' },
+    month: { autoComplete: 'bday-month', id: 'birthday-month' },
+    year: { autoComplete: 'bday-year', id: 'birthday-year' },
+  },
+};
diff --git a/components/input-field/src/stories.tsx b/components/input-field/src/stories.tsx
index 8e10d5d7..ce94db4c 100644
--- a/components/input-field/src/stories.tsx
+++ b/components/input-field/src/stories.tsx
@@ -47,3 +47,18 @@ InputWithHintTextError.args = {
   },
   children: 'National Insurance number',
 };
+
+export const InputWithTypeAndAutoComplete = Template.bind({});
+InputWithTypeAndAutoComplete.parameters = {
+  docs: {
+    description: {
+      story:
+        'Use the relevant autocomplete parameter to ensure your service is compliant with [WCAG 2.1 Level AA success criterion 1.3.5]((https://www.w3.org/WAI/WCAG21/Understanding/identify-input-purpose.html).',
+    },
+  },
+};
+InputWithTypeAndAutoComplete.args = {
+  input: { name: 'group1', autoComplete: 'email', type: 'email' },
+  hint: 'We’ll only use this to send you a receipt',
+  children: 'Email address',
+};


Test patch:
diff --git a/components/date-field/src/__snapshots__/test.tsx.snap b/components/date-field/src/__snapshots__/test.tsx.snap
index 4bc31df9..0923abba 100644
--- a/components/date-field/src/__snapshots__/test.tsx.snap
+++ b/components/date-field/src/__snapshots__/test.tsx.snap
@@ -203,6 +203,13 @@ exports[`DateField matches wrappersnapshot: wrapper mount 1`] = `
       "year": undefined,
     }
   }
+  inputs={
+    Object {
+      "day": undefined,
+      "month": undefined,
+      "year": undefined,
+    }
+  }
 >
   <src__StyledContainer
     errorText="example"
@@ -233,6 +240,13 @@ exports[`DateField matches wrappersnapshot: wrapper mount 1`] = `
           }
         }
         error={true}
+        inputs={
+          Object {
+            "day": undefined,
+            "month": undefined,
+            "year": undefined,
+          }
+        }
         names={
           Object {
             "day": undefined,
@@ -254,6 +268,13 @@ exports[`DateField matches wrappersnapshot: wrapper mount 1`] = `
             }
           }
           error={true}
+          inputs={
+            Object {
+              "day": undefined,
+              "month": undefined,
+              "year": undefined,
+            }
+          }
           labels={
             Object {
               "day": "Day",
@@ -556,6 +577,13 @@ exports[`DateField should support null value: null value 1`] = `
       "year": undefined,
     }
   }
+  inputs={
+    Object {
+      "day": undefined,
+      "month": undefined,
+      "year": undefined,
+    }
+  }
 >
   <src__StyledContainer>
     <div
@@ -577,6 +605,13 @@ exports[`DateField should support null value: null value 1`] = `
           }
         }
         error={false}
+        inputs={
+          Object {
+            "day": undefined,
+            "month": undefined,
+            "year": undefined,
+          }
+        }
         names={
           Object {
             "day": undefined,
@@ -598,6 +633,13 @@ exports[`DateField should support null value: null value 1`] = `
             }
           }
           error={false}
+          inputs={
+            Object {
+              "day": undefined,
+              "month": undefined,
+              "year": undefined,
+            }
+          }
           labels={
             Object {
               "day": "Day",
@@ -904,6 +946,13 @@ exports[`DateField should support setting value: value 1 2 3 1`] = `
       "year": undefined,
     }
   }
+  inputs={
+    Object {
+      "day": undefined,
+      "month": undefined,
+      "year": undefined,
+    }
+  }
 >
   <src__StyledContainer>
     <div
@@ -925,6 +974,13 @@ exports[`DateField should support setting value: value 1 2 3 1`] = `
           }
         }
         error={false}
+        inputs={
+          Object {
+            "day": undefined,
+            "month": undefined,
+            "year": undefined,
+          }
+        }
         names={
           Object {
             "day": undefined,
@@ -952,6 +1008,13 @@ exports[`DateField should support setting value: value 1 2 3 1`] = `
             }
           }
           error={false}
+          inputs={
+            Object {
+              "day": undefined,
+              "month": undefined,
+              "year": undefined,
+            }
+          }
           labels={
             Object {
               "day": "Day",