Skip to content

Commit

Permalink
Merge pull request #4442 from alphagov/show-hide-password-component
Browse files Browse the repository at this point in the history
Create password input component
  • Loading branch information
querkmachine authored Mar 15, 2024
2 parents 48d1523 + 2b5d976 commit 0978858
Show file tree
Hide file tree
Showing 26 changed files with 1,529 additions and 8 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ For advice on how to use these release notes see [our guidance on staying up to

## Unreleased

### New features

#### Use the Passwords input component to help users accessibly enter passwords

The [Password input component](https://design-system.service.gov.uk/components/password-input/) allows users to toggle the visibility of passwords and enter their passwords in plain text if they choose to do so.

This helps users use longer and more complex passwords without needing to remember what they've already typed.

This change was introduced in [pull request #4442: Create password input component](https://github.com/alphagov/govuk-frontend/pull/4442). Thanks to [@andysellick](https://github.com/andysellick) for the original contribution.

### Recommended changes

#### Update the HTML for the character count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ describe('Full page examples (with form submit)', () => {
title: 'Passport details',
path: '/full-page-examples/passport-details'
},
{
title: 'Sign in to a service',
path: '/full-page-examples/sign-in'
},
{
title: 'Update your account details',
path: '/full-page-examples/update-your-account-details'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { default as haveYouChangedYourName } from './have-you-changed-your-name/
export { default as feedback } from './feedback/index.mjs'
export { default as howDoYouWantToSignIn } from './how-do-you-want-to-sign-in/index.mjs'
export { default as search } from './search/index.mjs'
export { default as signIn } from './sign-in/index.mjs'
export { default as passportDetails } from './passport-details/index.mjs'
export { default as updateYourAccountDetails } from './update-your-account-details/index.mjs'
export { default as uploadYourPhoto } from './upload-your-photo/index.mjs'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "layouts/full-page-example.njk" %}

{% from "govuk/components/panel/macro.njk" import govukPanel %}

{% set pageTitle = "Logged in successfully" %}
{% block pageTitle %}{{ pageTitle }} - GOV.UK{% endblock %}

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
{{ govukPanel({
titleText: pageTitle
}) }}
</div>
</div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import express from 'express'
import { body, matchedData, validationResult } from 'express-validator'

import { formatValidationErrors } from '../../../utils.mjs'

const router = express.Router()

router.post(
'/sign-in',

body('email')
.notEmpty()
.withMessage('Enter your email address')
.isEmail()
.withMessage(
'Enter an email address in the correct format, like name@example.com'
),

body('password').notEmpty().withMessage('Enter your password'),

(req, res) => {
const { example } = res.locals

const viewPath = `./full-page-examples/${example.path}`
const errors = formatValidationErrors(validationResult(req))

if (!errors) {
return res.redirect(303, `./${example.path}/confirm`)
}

res.render(`${viewPath}/index`, {
errors,
errorSummary: Object.values(errors),
values: matchedData(req, { onlyValidData: false }) // In production this should sanitized.
})
}
)

export default router
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: Sign in to a service
name: Sign in to a service
scenario: |
As part of an online service, you have to sign in before accessing it.

Things to try:

1. Intentionally avoid answering the questions before continuing to the next page.
---

{% extends "layouts/full-page-example.njk" %}

{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/password-input/macro.njk" import govukPasswordInput %}
{% from "govuk/components/checkboxes/macro.njk" import govukCheckboxes %}
{% from "govuk/components/button/macro.njk" import govukButton %}

{% set pageTitle = example.title %}
{% block pageTitle %}{{ "Error: " if errorSummary | length }}{{ pageTitle }} - GOV.UK{% endblock %}

{% block beforeContent %}
{{ govukBackLink({
text: "Back"
}) }}
{% endblock %}

{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
{% if errorSummary | length %}
{{ govukErrorSummary({
titleText: "There is a problem",
errorList: errorSummary
}) }}
{% endif %}

<h1 class="govuk-heading-xl">{{ pageTitle }}</h1>

<p class="govuk-body">You'll first need to <a class="govuk-link" href="#">create an account</a> if you haven't already. You can also <a class="govuk-link" href="#">reset your password</a> if you can't remember it.</p>

<form method="post" novalidate>

{{ govukInput({
label: {
text: "Email address",
classes: "govuk-label--m"
},
type: "email",
id: "email",
name: "email",
value: values["email"],
errorMessage: errors["email"],
autocomplete: "email",
spellcheck: false
}) }}

{{ govukPasswordInput({
label: {
text: "Password",
classes: "govuk-label--m"
},
id: "password",
name: "password",
value: values["password"],
errorMessage: errors["password"],
autocomplete: "current-password"
}) }}

{{ govukCheckboxes({
name: "remember-me",
classes: "govuk-checkboxes--small",
items: [
{
value: "true",
text: "Keep me signed in on this device"
}
]
}) }}

{{ govukButton({
text: "Sign in"
}) }}

</form>
</div>
</div>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ router.post(
'Enter an email address in the correct format, like name@example.com'
),

body('password').notEmpty().withMessage('Enter your password'),
body('password').notEmpty().withMessage('Enter a password'),

(req, res) => {
const { example } = res.locals
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ scenario: |
{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
{% from "govuk/components/input/macro.njk" import govukInput %}
{% from "govuk/components/password-input/macro.njk" import govukPasswordInput %}
{% from "govuk/components/button/macro.njk" import govukButton %}

{% set pageTitle = example.title %}
Expand Down Expand Up @@ -52,18 +53,16 @@ scenario: |
spellcheck: false
}) }}

{{ govukInput({
{{ govukPasswordInput({
label: {
text: "New password",
classes: "govuk-label--m"
},
type: "password",
id: "password",
name: "password",
value: values["password"],
errorMessage: errors["password"],
autocomplete: "new-password",
spellcheck: false
autocomplete: "new-password"
}) }}

{{ govukButton({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ describe('GOV.UK Prototype Kit config', () => {
importFrom: 'govuk/components/panel/macro.njk',
macroName: 'govukPanel'
},
{
importFrom: 'govuk/components/password-input/macro.njk',
macroName: 'govukPasswordInput'
},
{
importFrom: 'govuk/components/phase-banner/macro.njk',
macroName: 'govukPhaseBanner'
Expand Down
4 changes: 3 additions & 1 deletion packages/govuk-frontend/src/govuk/all.jsdom.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jest.mock(`./components/error-summary/error-summary.mjs`)
jest.mock(`./components/exit-this-page/exit-this-page.mjs`)
jest.mock(`./components/header/header.mjs`)
jest.mock(`./components/notification-banner/notification-banner.mjs`)
jest.mock(`./components/password-input/password-input.mjs`)
jest.mock(`./components/radios/radios.mjs`)
jest.mock(`./components/skip-link/skip-link.mjs`)
jest.mock(`./components/tabs/tabs.mjs`)
Expand All @@ -27,7 +28,8 @@ describe('initAll', () => {
'character-count',
'error-summary',
'exit-this-page',
'notification-banner'
'notification-banner',
'password-input'
]

afterEach(() => {
Expand Down
5 changes: 5 additions & 0 deletions packages/govuk-frontend/src/govuk/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ErrorSummary } from './components/error-summary/error-summary.mjs'
import { ExitThisPage } from './components/exit-this-page/exit-this-page.mjs'
import { Header } from './components/header/header.mjs'
import { NotificationBanner } from './components/notification-banner/notification-banner.mjs'
import { PasswordInput } from './components/password-input/password-input.mjs'
import { Radios } from './components/radios/radios.mjs'
import { SkipLink } from './components/skip-link/skip-link.mjs'
import { Tabs } from './components/tabs/tabs.mjs'
Expand Down Expand Up @@ -41,6 +42,7 @@ function initAll(config) {
[ExitThisPage, config.exitThisPage],
[Header],
[NotificationBanner, config.notificationBanner],
[PasswordInput, config.passwordInput],
[Radios],
[SkipLink],
[Tabs]
Expand Down Expand Up @@ -81,6 +83,7 @@ export {
ExitThisPage,
Header,
NotificationBanner,
PasswordInput,
Radios,
SkipLink,
Tabs
Expand All @@ -96,6 +99,7 @@ export {
* @property {ErrorSummaryConfig} [errorSummary] - Error Summary config
* @property {ExitThisPageConfig} [exitThisPage] - Exit This Page config
* @property {NotificationBannerConfig} [notificationBanner] - Notification Banner config
* @property {PasswordInputConfig} [passwordInput] - Password input config
*/

/**
Expand All @@ -110,6 +114,7 @@ export {
* @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageConfig} ExitThisPageConfig
* @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageTranslations} ExitThisPageTranslations
* @typedef {import('./components/notification-banner/notification-banner.mjs').NotificationBannerConfig} NotificationBannerConfig
* @typedef {import('./components/password-input/password-input.mjs').PasswordInputConfig} PasswordInputConfig
*/

/**
Expand Down
1 change: 1 addition & 0 deletions packages/govuk-frontend/src/govuk/components/_all.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
@import "notification-banner/index";
@import "pagination/index";
@import "panel/index";
@import "password-input/index";
@import "phase-banner/index";
@import "radios/index";
@import "select/index";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('GOV.UK Frontend', () => {
'ExitThisPage',
'Header',
'NotificationBanner',
'PasswordInput',
'Radios',
'SkipLink',
'Tabs'
Expand Down
40 changes: 40 additions & 0 deletions packages/govuk-frontend/src/govuk/components/input/input.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ params:
type: boolean
required: false
description: Optional field to enable or disable the `spellcheck` attribute on the input.
- name: autocapitalize
type: string
required: false
description: Optional field to enable or disable autocapitalisation of user input. See [autocapitalization](https://html.spec.whatwg.org/multipage/interaction.html#autocapitalization) for a full list of values that can be used.
- name: inputWrapper
type: object
required: false
description: If any of `prefix`, `suffix`, `formGroup.beforeInput` or `formGroup.afterInput` have a value, a wrapping element is added around the input and inserted content. This object allows you to customise that wrapping element.
params:
- name: classes
type: string
required: false
description: Classes to add to the wrapping element.
- name: attributes
type: object
required: false
description: HTML attributes (for example data attributes) to add to the wrapping element.
- name: attributes
type: object
required: false
Expand Down Expand Up @@ -279,6 +296,14 @@ examples:
name: spellcheck
type: text
spellcheck: false
- name: with autocapitalize turned off
options:
label:
text: Autocapitalize is turned off
id: input-with-autocapitalize-off
name: autocapitalize
type: text
autocapitalize: none

- name: with prefix
options:
Expand Down Expand Up @@ -522,3 +547,18 @@ examples:
html: <span>kg</span>
attributes:
data-attribute: value
- name: with customised input wrapper
hidden: true
options:
label:
text: Cost per item, in pounds
id: input-with-customised-input-wrapper
name: cost
inputWrapper:
classes: app-input-wrapper--custom-modifier
attributes:
data-attribute: value
prefix:
text: £
suffix:
text: per item
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
{%- if params.autocomplete %} autocomplete="{{ params.autocomplete }}"{% endif %}
{%- if params.pattern %} pattern="{{ params.pattern }}"{% endif %}
{%- if params.inputmode %} inputmode="{{ params.inputmode }}"{% endif %}
{%- if params.autocapitalize %} autocapitalize="{{ params.autocapitalize }}"{% endif %}
{{- govukAttributes(params.attributes) }}>
{%- endmacro -%}

Expand Down Expand Up @@ -65,7 +66,8 @@
{% endif %}

{%- if hasPrefix or hasSuffix or hasBeforeInput or hasAfterInput %}
<div class="govuk-input__wrapper">
<div class="govuk-input__wrapper {%- if params.inputWrapper.classes %} {{ params.inputWrapper.classes }}{% endif %}"
{{- govukAttributes(params.inputWrapper.attributes) }}>
{% if hasBeforeInput %}
{{- params.formGroup.beforeInput.html | safe | trim | indent(4, true) if params.formGroup.beforeInput.html else params.formGroup.beforeInput.text }}
{% endif %}
Expand Down
Loading

0 comments on commit 0978858

Please sign in to comment.