diff --git a/CHANGELOG.md b/CHANGELOG.md index 255b89fc4f..7390b8b4d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ ### New features +#### Add prefix and suffix for text input + +You can now add a prefix or suffix element to the text input to help users enter things like currencies and measurements. + +For example: + +```javascript +{{ govukInput({ + label: { + text: "Amount, in pounds" + }, + prefix: { + text: "£" + } +}) }} +``` + +You shouldn't rely on prefixes or suffixes alone because people who use a screen reader won't see them. You should add any specific type of information you need to the input label or hint text as well. + +This was added in [pull request #1816: Add input prefix and suffix](https://github.com/alphagov/govuk-frontend/pull/1816). + #### Add HTML test fixtures You can use our test fixtures to check you're outputting the same HTML that GOV.UK Frontend uses. diff --git a/src/govuk/components/input/_index.scss b/src/govuk/components/input/_index.scss index 17632e077d..50e2659fe7 100644 --- a/src/govuk/components/input/_index.scss +++ b/src/govuk/components/input/_index.scss @@ -91,4 +91,87 @@ max-width: 5.4ex; } + .govuk-input__wrapper { + display: flex; + + .govuk-input { + flex: 0 1 auto; + } + + .govuk-input:focus { + // Hack to stop focus style being overlapped by the suffix + z-index: 1; + } + + // Split prefix/suffix onto separate lines on narrow screens + @include govuk-media-query($until: mobile) { + display: block; + + .govuk-input { + // Set max-width to override potential width override class on the input + max-width: 100%; + } + } + } + + .govuk-input__prefix, + .govuk-input__suffix { + @include govuk-font($size: 19); + + box-sizing: border-box; + display: inline-block; + min-width: 40px; + @if $govuk-typography-use-rem { + min-width: govuk-px-to-rem(40px); + } + height: 40px; + @if $govuk-typography-use-rem { + height: govuk-px-to-rem(40px); + } + + padding: govuk-spacing(1); + border: $govuk-border-width-form-element solid $govuk-input-border-colour; + background-color: govuk-colour("light-grey", $legacy: "grey-3"); + + text-align: center; + @include govuk-media-query($until: tablet) { + line-height: 1.6; + } + white-space: nowrap; + + // Emphasise non-editable status of prefixes and suffixes + cursor: default; + + flex: 0 0 auto; + + // Split prefix/suffix onto separate lines on narrow screens + @include govuk-media-query($until: mobile) { + display: block; + height: 100%; + white-space: normal; + } + } + + .govuk-input__prefix { + @include govuk-media-query($until: mobile) { + border-bottom: 0; + } + @include govuk-media-query($from: mobile) { + @include govuk-not-ie8 { + border-right: 0; + } + } + } + + // Split prefix/suffix onto separate lines on narrow screens + .govuk-input__suffix { + @include govuk-media-query($until: mobile) { + border-top: 0; + } + @include govuk-media-query($from: mobile) { + @include govuk-not-ie8 { + border-left: 0; + } + } + } } diff --git a/src/govuk/components/input/input.yaml b/src/govuk/components/input/input.yaml index b917dea526..9daffe5478 100644 --- a/src/govuk/components/input/input.yaml +++ b/src/govuk/components/input/input.yaml @@ -38,6 +38,48 @@ params: required: false description: Options for the error message component. The error message component will not display if you use a falsy value for `errorMessage`, for example `false` or `null`. isComponent: true +- name: prefix + type: object + required: false + description: Options for the prefix element. + params: + - name: text + type: string + required: true + description: Required. If `html` is set, this is not required. Text to use within the label. If `html` is provided, the `text` argument will be ignored. + - name: html + type: string + required: true + description: Required. If `text` is set, this is not required. HTML to use within the label. If `html` is provided, the `text` argument will be ignored. + - name: classes + type: string + required: false + description: Classes to add to the prefix. + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the prefix element. +- name: suffix + type: object + required: false + description: Options for the suffix element. + params: + - name: text + type: string + required: true + description: Required. If `html` is set, this is not required. Text to use within the label. If `html` is provided, the `text` argument will be ignored. + - name: html + type: string + required: true + description: Required. If `text` is set, this is not required. HTML to use within the label. If `html` is provided, the `text` argument will be ignored. + - name: classes + type: string + required: false + description: Classes to add to the suffix element. + - name: attributes + type: object + required: false + description: HTML attributes (for example data attributes) to add to the suffix element. - name: formGroup type: object required: false @@ -203,6 +245,68 @@ examples: type: text spellcheck: false + - name: with prefix + data: + label: + text: Amount, in pounds + id: input-with-prefix + name: amount + prefix: + text: £ + + - name: with suffix + data: + label: + text: Weight, in kilograms + id: input-with-suffix + name: weight + suffix: + text: kg + + - name: with prefix and suffix + data: + label: + text: Cost per item, in pounds + id: input-with-prefix-suffix + name: cost + prefix: + text: £ + suffix: + text: per item + - name: with prefix and long suffix + data: + label: + text: Cost per item, in pounds, per household member + id: input-with-prefix-suffix + name: cost + prefix: + text: £ + suffix: + text: per household member + - name: with prefix and suffix and error + data: + label: + text: Cost per item, in pounds + id: input-with-prefix-suffix + name: cost + prefix: + text: £ + suffix: + text: per item + errorMessage: + text: Error message goes here + - name: with prefix and suffix and width modifier + data: + label: + text: Cost per item, in pounds + id: input-with-prefix-suffix + name: cost + classes: govuk-input--width-5 + prefix: + text: £ + suffix: + text: per item + # Hidden examples are not shown in the review app, but are used for tests and HTML fixtures - name: classes hidden: true @@ -257,3 +361,82 @@ examples: data: inputmode: decimal + - name: with prefix with html as text + hidden: true + data: + label: + text: Amount, in pounds + id: input-with-prefix + name: amount + prefix: + text: £ + - name: with prefix with html + hidden: true + data: + label: + html: Amount, in pounds + id: input-with-prefix + name: amount + prefix: + html: £ + - name: with prefix with classes + hidden: true + data: + label: + text: Amount, in pounds + id: input-with-prefix-element + name: amount + prefix: + text: £ + classes: app-input__prefix--custom-modifier + - name: with prefix with attributes + hidden: true + data: + label: + text: Amount, in pounds + id: input-with-prefix-element + name: amount + prefix: + text: £ + attributes: + data-attribute: value + + - name: with suffix with html as text + hidden: true + data: + label: + text: Weight, in kilograms + id: input-with-suffix + name: weight + suffix: + text: kg + - name: with suffix with html + hidden: true + data: + label: + text: Weight, in kilograms + id: input-with-suffix + name: weight + suffix: + html: kg + - name: with suffix with classes + hidden: true + data: + label: + text: Weight, in kilograms + id: input-with-suffix + name: weight + suffix: + html: kg + classes: app-input__suffix--custom-modifier + - name: with suffix with attributes + hidden: true + data: + label: + text: Weight, in kilograms + id: input-with-suffix + name: weight + suffix: + html: kg + attributes: + data-attribute: value diff --git a/src/govuk/components/input/template.njk b/src/govuk/components/input/template.njk index 25dcb50883..11dd50c013 100644 --- a/src/govuk/components/input/template.njk +++ b/src/govuk/components/input/template.njk @@ -37,6 +37,15 @@ visuallyHiddenText: params.errorMessage.visuallyHiddenText }) | indent(2) | trim }} {% endif %} + +{%- if params.prefix or params.suffix %}
{% endif -%} + + {%- if params.prefix.text or params.prefix.html %} + + {% endif -%} + + + {%- if params.suffix.text or params.suffix.html %} + + {% endif -%} + +{%- if params.prefix or params.suffix %}
{% endif %} diff --git a/src/govuk/components/input/template.test.js b/src/govuk/components/input/template.test.js index d750956280..099a433505 100644 --- a/src/govuk/components/input/template.test.js +++ b/src/govuk/components/input/template.test.js @@ -99,6 +99,13 @@ describe('Input', () => { const $formGroup = $('.govuk-form-group') expect($formGroup.hasClass('extra-class')).toBeTruthy() }) + + it('doesn\'t render the input wrapper', () => { + const $ = render('input', examples.default) + + const $wrapper = $('.govuk-form-group > .govuk-input__wrapper') + expect($wrapper.length).toBeFalsy() + }) }) describe('when it includes a hint', () => { @@ -281,4 +288,135 @@ describe('Input', () => { expect($component.attr('inputmode')).toEqual('decimal') }) }) + + describe('when it includes a prefix', () => { + it('renders the input wrapper', () => { + const $ = render('input', examples['with prefix']) + + const $wrapper = $('.govuk-form-group > .govuk-input__wrapper') + expect($wrapper.length).toBeTruthy() + }) + + it('renders the prefix inside the wrapper', () => { + const $ = render('input', examples['with prefix']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + expect($prefix.length).toBeTruthy() + }) + + it('renders the text in the prefix', () => { + const $ = render('input', examples['with prefix']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + + expect($prefix.html()).toEqual('£') + }) + + it('allows prefix text to be passed whilst escaping HTML entities', () => { + const $ = render('input', examples['with prefix with html as text']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + + expect($prefix.html()).toEqual('<span>£</span>') + }) + + it('allows prefix HTML to be passed un-escaped', () => { + const $ = render('input', examples['with prefix with html']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + + expect($prefix.html()).toEqual('£') + }) + + it('hides the prefix from screen readers using the aria-hidden attribute', () => { + const $ = render('input', examples['with prefix']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + expect($prefix.attr('aria-hidden')).toEqual('true') + }) + + it('renders with classes', () => { + const $ = render('input', examples['with prefix with classes']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + expect($prefix.hasClass('app-input__prefix--custom-modifier')).toBeTruthy() + }) + + it('renders with attributes', () => { + const $ = render('input', examples['with prefix with attributes']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + expect($prefix.attr('data-attribute')).toEqual('value') + }) + }) + + describe('when it includes a suffix', () => { + it('renders the input wrapper', () => { + const $ = render('input', examples['with suffix']) + + const $wrapper = $('.govuk-form-group > .govuk-input__wrapper') + expect($wrapper.length).toBeTruthy() + }) + + it('renders the suffix inside the wrapper', () => { + const $ = render('input', examples['with suffix']) + + const $suffix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__suffix') + expect($suffix.length).toBeTruthy() + }) + + it('renders the text in the prefix', () => { + const $ = render('input', examples['with prefix']) + + const $prefix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix') + + expect($prefix.html()).toEqual('£') + }) + + it('allows suffix text to be passed whilst escaping HTML entities', () => { + const $ = render('input', examples['with suffix with html as text']) + + const $suffix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__suffix') + + expect($suffix.html()).toEqual('<span>kg</span>') + }) + + it('allows suffix HTML to be passed un-escaped', () => { + const $ = render('input', examples['with suffix with html']) + + const $suffix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__suffix') + + expect($suffix.html()).toEqual('kg') + }) + + it('hides the suffix from screen readers using the aria-hidden attribute', () => { + const $ = render('input', examples['with suffix']) + + const $suffix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__suffix') + expect($suffix.attr('aria-hidden')).toEqual('true') + }) + + it('renders with classes', () => { + const $ = render('input', examples['with suffix with classes']) + + const $suffix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__suffix') + expect($suffix.hasClass('app-input__suffix--custom-modifier')).toBeTruthy() + }) + + it('renders with attributes', () => { + const $ = render('input', examples['with suffix with attributes']) + + const $suffix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__suffix') + expect($suffix.attr('data-attribute')).toEqual('value') + }) + }) + + describe('when it includes both a prefix and a suffix', () => { + it('renders the prefix before the suffix', () => { + const $ = render('input', examples['with prefix and suffix']) + + const $prefixBeforeSuffix = $('.govuk-form-group > .govuk-input__wrapper > .govuk-input__prefix ~ .govuk-input__suffix') + expect($prefixBeforeSuffix.length).toBeTruthy() + }) + }) })