Migrate GOV.UK components to USWDS

- Convert previous/next navigation to USWDS pagination component
  - Update radio buttons and checkboxes to use USWDS classes
  - Migrate guest list inputs with proper USWDS styling
  - Replace GOV.UK button patterns with USWDS button variants
  - Update form field spacing to use USWDS utilities
  - Scope fieldset legend styles to specific form contexts
  - Update visual regression test references for new components
This commit is contained in:
alexjanousekGSA
2025-07-30 15:24:21 -04:00
parent fa2e5becea
commit 9e97a797b9
45 changed files with 179 additions and 81 deletions

View File

@@ -17,6 +17,7 @@
this.minEntries = 2;
this.listItemName = this.$wrapper.data('listItemName');
this.getSharedAttributes();
this.getOriginalClasses();
this.getValues();
this.maxEntries = this.entries.length;
@@ -26,30 +27,31 @@
};
ListEntry.optionalAttributes = ['aria-describedby'];
ListEntry.prototype.entryTemplate = Hogan.compile(
'<div class="list-entry">' +
'<label for="{{{id}}}" class="govuk-input--numbered__label">' +
'<div class="list-entry margin-bottom-2">' +
'<label for="{{{id}}}" class="usa-label">' +
'<span class="usa-sr-only">{{listItemName}} number </span>{{number}}.' +
'</label>' +
'<input' +
' class="usa-input {{classes}}"' +
' name="{{name}}"' +
' id="{{id}}"' +
' {{#value}}value="{{value}}{{/value}}"' +
' {{{sharedAttributes}}}' +
'/>' +
'{{#button}}' +
'<button type="button" class="usa-button input-list__button--remove">' +
'<button type="button" class="usa-button--unstyled text-primary margin-top-1 input-list__button--remove">' +
'Remove<span class="usa-sr-only"> {{listItemName}} number {{number}}</span>' +
'</button>' +
'{{/button}}' +
'</div>'
);
ListEntry.prototype.addButtonTemplate = Hogan.compile(
'<button type="button" class="usa-button input-list__button--add">Add another {{listItemName}} ({{entriesLeft}} remaining)</button>'
'<button type="button" class="usa-button usa-button--outline input-list__button--add margin-top-2">Add another {{listItemName}} ({{entriesLeft}} remaining)</button>'
);
ListEntry.prototype.getSharedAttributes = function () {
var $inputs = this.$wrapper.find('input'),
attributeTemplate = Hogan.compile(' {{name}}="{{value}}"'),
generatedAttributes = ['id', 'name', 'value'],
generatedAttributes = ['id', 'name', 'value', 'class'],
attributes = [],
attrIdx,
elmAttributes,
@@ -94,6 +96,27 @@
this.sharedAttributes = (attributes.length) ? getAttributesHTML(attributes) : '';
};
ListEntry.prototype.getOriginalClasses = function () {
var $firstInput = this.$wrapper.find('input').first();
if ($firstInput.length) {
var classList = $firstInput.attr('class');
if (classList) {
// Remove any GOV.UK or USA numbered classes, keep others
this.additionalClasses = classList
.split(' ')
.filter(function(cls) {
return cls &&
!cls.match(/^(govuk-input|usa-input)/) &&
cls !== 'usa-input--numbered';
})
.join(' ');
} else {
this.additionalClasses = '';
}
} else {
this.additionalClasses = '';
}
};
ListEntry.prototype.getValues = function () {
this.entries = [];
this.$wrapper.find('input').each(function (idx, elm) {
@@ -182,7 +205,8 @@
'name' : this.getId(entryNumber),
'value' : entry,
'listItemName' : this.listItemName,
'sharedAttributes': this.sharedAttributes
'sharedAttributes': this.sharedAttributes,
'classes': this.additionalClasses
};
if (entryNumber > 1) {

View File

@@ -7,9 +7,6 @@ $(() => GOVUK.modules.start());
$(() => $('.error-message, .usa-error-message').eq(0).parent('label').next('input').trigger('focus'));
$(() => $('.govuk-header__container').on('click', function() {
$(this).css('border-color', '#005ea5');
}));
// Applies our expanded focus style to the siblings of links when that link is wrapped in a heading.
//

View File

@@ -1092,3 +1092,8 @@ nav.nav {
.selection-content .usa-form-group--nested li.usa-checkbox {
margin-top: 0;
}
// Global styling for radio button legends
fieldset legend.usa-legend {
font-weight: 700;
}

View File

@@ -0,0 +1,20 @@
{% from "components/select-input.html" import select, select_list, select_nested, select_wrapper, select_input %}
{% macro checkboxes(field, hint=None, disable=[], option_hints={}, hide_legend=False) %}
{{ select(field, hint, disable, option_hints, hide_legend, input="checkbox") }}
{% endmacro %}
{% macro checkbox_list(options, child_map, disable=[], option_hints={}) %}
{{ select_list(options, child_map, disable, option_hints, input="checkbox") }}
{% endmacro %}
{% macro checkboxes_nested(field, child_map, hint=None, disable=[], option_hints={}, hide_legend=False) %}
{{ select_nested(field, child_map, hint, disable, option_hints, hide_legend, input="checkbox") }}
{% endmacro %}
{% macro checkbox(option, disable=[], option_hints={}, data_target=None, as_list_item=False) %}
{{ select_input(option, disable, option_hints, data_target, as_list_item, input="checkbox") }}
{% endmacro %}

View File

@@ -36,9 +36,9 @@
{% set autocomplete = "" %}
{% endif %}
{% if entry.errors %}
{% set label_classes = "usa-input--numbered__label usa-input--numbered__label--error" %}
{% set label_classes = "usa-label usa-label--error" %}
{% else %}
{% set label_classes = "usa-input--numbered__label" %}
{% set label_classes = "usa-label" %}
{% endif %}
{% set field_name = field.name + "-" + loop.index|string %}
{{ entry(param_extensions={
@@ -48,7 +48,7 @@
"html": '<span class="usa-sr-only">' + item_name + ' number </span>' + loop.index|string + '.',
"classes": label_classes
},
"classes": "usa-input--numbered ",
"classes": "usa-input",
"value": field.data[loop.index0],
"autocomplete": autocomplete
}) }}

View File

@@ -1,30 +1,30 @@
{% macro previous_next_navigation(previous_page, next_page) %}
{% if previous_page or next_page %}
<nav class="govuk-previous-and-next-navigation" role="navigation" aria-label="Pagination">
<ul class="group">
<nav class="usa-pagination margin-y-5 text-left" role="navigation" aria-label="Pagination">
<ul class="usa-pagination__list flex-justify-start">
{% if previous_page %}
<li class="previous-page">
<a class="usa-link" href="{{previous_page['url']}}" rel="previous" >
<span class="pagination-part-title">
<svg class="pagination-icon" xmlns="http://www.w3.org/2000/svg" height="13" width="17" viewBox="0 0 17 13">
<path fill="currentColor" d="m6.5938-0.0078125-6.7266 6.7266 6.7441 6.4062 1.377-1.449-4.1856-3.9768h12.896v-2h-12.984l4.2931-4.293-1.414-1.414z"/>
</svg>
<li class="usa-pagination__item usa-pagination__arrow">
<a class="usa-pagination__link usa-pagination__previous-page" href="{{previous_page['url']}}" rel="previous">
<svg class="usa-icon" xmlns="http://www.w3.org/2000/svg" height="13" width="17" viewBox="0 0 17 13">
<path fill="currentColor" d="m6.5938-0.0078125-6.7266 6.7266 6.7441 6.4062 1.377-1.449-4.1856-3.9768h12.896v-2h-12.984l4.2931-4.293-1.414-1.414z"/>
</svg>
<span class="usa-pagination__link-text">
{{previous_page['title']}}
</span>
<span class="pagination-label">{{previous_page['label']}}</span>
<span class="usa-sr-only">{{previous_page['label']}}</span>
</a>
</li>
{% endif %}
{% if next_page %}
<li class="next-page">
<a class="usa-link" href="{{next_page['url']}}" rel="next">
<span class="pagination-part-title">
<li class="usa-pagination__item usa-pagination__arrow">
<a class="usa-pagination__link usa-pagination__next-page" href="{{next_page['url']}}" rel="next">
<span class="usa-pagination__link-text">
{{next_page['title']}}
<svg class="pagination-icon" xmlns="http://www.w3.org/2000/svg" height="13" width="17" viewBox="0 0 17 13">
<path fill="currentColor" d="m10.107-0.0078125-1.4136 1.414 4.2926 4.293h-12.986v2h12.896l-4.1855 3.9766 1.377 1.4492 6.7441-6.4062-6.7246-6.7266z"/>
</svg>
</span>
<span class="pagination-label">{{next_page['label']}}</span>
<svg class="usa-icon" xmlns="http://www.w3.org/2000/svg" height="13" width="17" viewBox="0 0 17 13">
<path fill="currentColor" d="m10.107-0.0078125-1.4136 1.414 4.2926 4.293h-12.986v2h12.896l-4.1855 3.9766 1.377 1.4492 6.7441-6.4062-6.7246-6.7266z"/>
</svg>
<span class="usa-sr-only">{{next_page['label']}}</span>
</a>
</li>
{% endif %}

View File

@@ -64,9 +64,9 @@
{% endmacro %}
{% macro select_input(option, disable=[], option_hints={}, data_target=None, as_list_item=False, input="radio") %}
<div class="usa-radio" {% if data_target %}data-target="{{ data_target }}"{% endif %}>
<div class="usa-{{ input }}" {% if data_target %}data-target="{{ data_target }}"{% endif %}>
<input
id="{{ option.id }}" class="usa-radio__input" name="{{ option.name }}" type="{{ input }}" value="{{ option.data }}"
id="{{ option.id }}" class="usa-{{ input }}__input" name="{{ option.name }}" type="{{ input }}" value="{{ option.data }}"
{% if option.data in disable %}
disabled
{% endif %}
@@ -74,7 +74,7 @@
checked
{% endif %}
>
<label class="usa-radio__label" for="{{ option.id }}">
<label class="usa-{{ input }}__label" for="{{ option.id }}">
{{ option.label.text }}
{% if option_hints[option.data] %}
<span class="usa-hint">

View File

@@ -4,6 +4,7 @@
{% from "components/banner.html" import banner_wrapper %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/radios.html" import radios %}
{% block service_page_title %}
Create an API key
@@ -19,7 +20,19 @@
{% call form_wrapper() %}
{{ form.key_name }}
{{ form.key_type }}
<div class="margin-top-4 margin-bottom-3">
<div class="api-key-radios">
{% if current_service.trial_mode %}
{{ radios(
form.key_type,
disable=['normal'],
option_hints={'normal': 'Not available because your service is in <a class="usa-link" href="/features/trial-mode">trial mode</a>'|safe}
) }}
{% else %}
{{ radios(form.key_type) }}
{% endif %}
</div>
</div>
{{ page_footer('Continue') }}
{% endcall %}

View File

@@ -3,6 +3,7 @@
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/radios.html" import radios %}
{% block per_page_title %}
Set auth type for {{ user.name }}
@@ -19,7 +20,9 @@
{{ page_header('Set auth type for ' + user.name) }}
{% call form_wrapper() %}
{{ form.auth_type }}
<div class="auth-type-radios">
{{ radios(form.auth_type) }}
</div>
{{ page_footer('Save') }}
{% endcall %}
</div>

View File

@@ -11,13 +11,15 @@
{% endif %}
{% if service_has_email_auth %}
{% if not mobile_number %}
{{ radios(
form.login_authentication,
disable=['sms_auth'],
option_hints={'sms_auth': 'Not available because this team member has not added a phone&nbsp;number to their profile'|safe}
) }}
{% else %}
{{ radios(form.login_authentication) }}
{% endif %}
<div class="login-auth-radios margin-top-4">
{% if not mobile_number %}
{{ radios(
form.login_authentication,
disable=['sms_auth'],
option_hints={'sms_auth': 'Not available because this team member has not added a phone&nbsp;number to their profile'|safe}
) }}
{% else %}
{{ radios(form.login_authentication) }}
{% endif %}
</div>
{% endif %}

View File

@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/radios.html" import radios %}
{% block per_page_title %}
Create an account
@@ -40,7 +41,7 @@ Create an account
{{ page_footer("Continue") }}
{{form.service}}
{{form.email_address}}
{{form.auth_type}}
{{ radios(form.auth_type) }}
{% endcall %}
</div>
</div>

View File

@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/radios.html" import radios %}
{% block per_page_title %}
Create an account
@@ -26,7 +27,7 @@ Create an account
}) }}
</div>
{{ form.password(param_extensions={"hint": {"text": "At least 8 characters"}, "autocomplete": "new-password"}) }}
{{form.auth_type}}
{{ radios(form.auth_type) }}
{{ page_footer("Continue") }}
{% endcall %}
</div>

View File

@@ -3,6 +3,7 @@
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/radios.html" import radios %}
{% block service_page_title %}
Set data retention
@@ -16,7 +17,9 @@
{{ page_header('Set data retention') }}
{% call form_wrapper() %}
{{ form.notification_type }}
<div class="notification-type-radios">
{{ radios(form.notification_type) }}
</div>
{{ form.days_of_retention }}
{{ page_footer('Add') }}
{% endcall %}

View File

@@ -4,6 +4,7 @@
{% from "components/live-search.html" import live_search %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/radios.html" import radios %}
{% set page_title = "Link service to organization" %}
@@ -26,7 +27,9 @@
) }}
{% call form_wrapper(data_force_focus=True) %}
{% if has_organizations %}
{{ form.organizations }}
<div class="organizations-radios">
{{ radios(form.organizations) }}
</div>
{{ sticky_page_footer('Save') }}
{% else %}
<p class="hint"> No organizations </p>

View File

@@ -2,6 +2,7 @@
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/select/macro.njk" import usaSelect -%}
{% from "components/radios.html" import radios %}
{% block per_page_title %}
Set up your profile
@@ -68,7 +69,7 @@ Set up your profile
]
})
}}-->
{{form.auth_type}}
{{ radios(form.auth_type) }}
{{ page_footer("Save") }}
{% endcall %}
</div>

View File

@@ -1,3 +1,5 @@
{% from "components/checkboxes.html" import checkboxes %}
{% macro format_item_name(name, separators=True) -%}
{%- if name is string -%}
{{- name -}}

View File

@@ -2,6 +2,7 @@
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/radios.html" import radios %}
{% block service_page_title %}
{{ sender_context.title }}
@@ -15,15 +16,7 @@
<div class="grid-row">
{% call form_wrapper() %}
{{ form.sender(param_extensions={
'fieldset': {
'legend': {
'isPageHeading': True,
'text': sender_context.title
}
},
'classes': 'grid-col-9'
}) }}
{{ radios(form.sender) }}
<div class="grid-col-9">
{{ page_footer('Continue') }}
</div>

View File

@@ -2,6 +2,7 @@
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/radios.html" import radios %}
{% set page_title = 'Set letter contact block' %}
@@ -17,18 +18,9 @@
<div class="grid-row">
{% call form_wrapper() %}
{{ form.sender(param_extensions={
'fieldset': {
'legend': {
'isPageHeading': True,
'text': page_title
}
},
'classes': 'grid-col-9'
}) }}
{{ radios(form.sender) }}
<div class="grid-col-9">
{{ page_footer('Continue') }}
<a class="usa-link" href="{{ url_for('.service_add_letter_contact', service_id=current_service.id, from_template=template_id) }}">Add new sender</a>
</div>
{% endcall %}
</div>