Skip to content

Commit 5fe8a36

Browse files
authored
Merge pull request #5166 from alphagov/app-promo-component-close-icon
Add a new app promo banner component
2 parents a04557f + 8c766ef commit 5fe8a36

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+1174
-269
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
## Unreleased
1111

1212
* Remove options from public layout ([PR #5214](https://github.com/alphagov/govuk_publishing_components/pull/5214))
13+
* Add a new app promo banner component ([PR #5166](https://github.com/alphagov/govuk_publishing_components/pull/5166))
1314

1415
## 63.2.0
1516

3.78 KB
Loading

app/assets/javascripts/govuk_publishing_components/analytics-ga4/ga4-page-views.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ window.GOVUK.analyticsGa4.analyticsModules = window.GOVUK.analyticsGa4.analytics
5959
emergency_banner: document.querySelector('[data-ga4-emergency-banner]') ? 'true' : undefined,
6060
phase_banner: this.getElementAttribute('data-ga4-phase-banner') || undefined,
6161
devolved_nations_banner: this.getElementAttribute('data-ga4-devolved-nations-banner') || undefined,
62+
app_promo_banner: this.getBannerPresence('[data-ga4-app-promo-banner]'),
6263
cookie_banner: this.getBannerPresence('[data-ga4-cookie-banner]'),
6364
intervention: this.getBannerPresence('[data-ga4-intervention-banner]'),
6465
global_bar: this.getBannerPresence('[data-ga4-global-banner]'),
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
window.GOVUK = window.GOVUK || {}
2+
window.GOVUK.Modules = window.GOVUK.Modules || {};
3+
4+
(function (Modules) {
5+
class AppPromoBanner {
6+
constructor ($module) {
7+
this.$module = $module
8+
/** @type {HTMLButtonElement | null} */
9+
this.$closeButton = this.$module.querySelector('.js-close-app-promo-banner')
10+
this.APP_PROMO_BANNER_COOKIE = 'app_promo_banner'
11+
}
12+
13+
/**
14+
* Initialise the app promo banner
15+
*
16+
*/
17+
init () {
18+
// Only display the banner on Android devices
19+
if (!this.isAndroidDevice()) return
20+
21+
// Do not display the app promo banner if the cookie banner is visible
22+
if (this.isCookieBannerVisible()) return
23+
24+
// Do not display the banner if it was previously closed
25+
const cookieContents = this.getAppPromoBannerCookieContent()
26+
if (cookieContents && cookieContents.closed === true) return
27+
28+
// Do not display the banner if the close button is not found
29+
if (!this.$closeButton) return
30+
31+
this.$closeButton.addEventListener('click', (event) => this.handleClick(event))
32+
this.showBanner()
33+
}
34+
35+
/**
36+
* Checks if the current device is an Android device
37+
*
38+
* @returns Boolean
39+
*/
40+
isAndroidDevice () {
41+
return (/Android/i.test(navigator.userAgent))
42+
}
43+
44+
/**
45+
* Checks if the user has accepted cookies and the cookie
46+
* category the app promo banner is in
47+
*
48+
* @returns Boolean
49+
*/
50+
isCookiesAndCategoryPermitted () {
51+
const cookieConsent = window.GOVUK.getConsentCookie()
52+
const cookieCategory = window.GOVUK.getCookieCategory(this.APP_PROMO_BANNER_COOKIE)
53+
return (cookieConsent && cookieConsent[cookieCategory])
54+
}
55+
56+
/**
57+
* Checks if the cookie banner is visible
58+
*
59+
* @returns Boolean - Cookie banner visible
60+
*/
61+
isCookieBannerVisible () {
62+
/** @type {HTMLDivElement | null} */
63+
const cookieBanner = document.querySelector('#global-cookie-message')
64+
return cookieBanner && !cookieBanner.hidden
65+
}
66+
67+
/**
68+
* Sets the cookie for the app promo banner, including if the
69+
* the banner should be in the closed state
70+
*
71+
* @param {Boolean} isAppBannerClosed
72+
*/
73+
setAppBannerCookie (isAppBannerClosed) {
74+
// Do not set the cookie if cookies and category are not permitted
75+
if (!this.isCookiesAndCategoryPermitted) return
76+
77+
/** @type {AppBannerCookie} */
78+
const appBannerCookie = { closed: isAppBannerClosed }
79+
80+
// This approach will simply set/overwrite the cookie with new values
81+
const appBannerCookieValue = JSON.stringify(appBannerCookie)
82+
window.GOVUK.setCookie(this.APP_PROMO_BANNER_COOKIE, appBannerCookieValue, { days: 84 })
83+
}
84+
85+
/**
86+
* Return the user's cookie settings for the app promo banner,
87+
* or null if the cookie is not present
88+
*
89+
* @returns {AppBannerCookie | null} App promo banner settings
90+
*/
91+
getAppPromoBannerCookieContent () {
92+
const appBannerCookie = window.GOVUK.getCookie(this.APP_PROMO_BANNER_COOKIE)
93+
return appBannerCookie ? JSON.parse(appBannerCookie) : null
94+
}
95+
96+
/**
97+
* Handle the click of the close banner button
98+
*
99+
* @param {MouseEvent} event - Click event
100+
*/
101+
handleClick (event) {
102+
event.preventDefault()
103+
this.hideBanner()
104+
}
105+
106+
/**
107+
* Shows the app promo banner
108+
*
109+
*/
110+
showBanner () {
111+
this.$module.hidden = false
112+
this.$module.classList.add('gem-c-app-promo-banner--visible')
113+
}
114+
115+
/**
116+
* Hides the app promo banner and sets a cookie to store
117+
* the closed state as true
118+
*
119+
*/
120+
hideBanner () {
121+
this.setAppBannerCookie(true)
122+
this.$module.hidden = true
123+
this.$module.classList.remove('gem-c-app-promo-banner--visible')
124+
}
125+
}
126+
127+
Modules.AppPromoBanner = AppPromoBanner
128+
})(window.GOVUK.Modules)
129+
130+
/**
131+
* @typedef {object} AppBannerCookie
132+
* @property {boolean} [closed] - Banner closed state
133+
* @property {number} [version] - Banner version number
134+
*/

app/assets/javascripts/govuk_publishing_components/lib/cookie-functions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
licensing_session: 'essential',
2222
govuk_contact_referrer: 'essential',
2323
multivariatetest_cohort_coronavirus_extremely_vulnerable_rate_limit: 'essential',
24+
app_promo_banner: 'settings',
2425
dgu_beta_banner_dismissed: 'settings',
2526
global_banner_seen: 'settings',
2627
user_nation: 'settings',

app/assets/stylesheets/govuk_publishing_components/_all_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
@import "components/accordion";
1212
@import "components/action-link";
1313
@import "components/add-another";
14+
@import "components/app-promo-banner";
1415
@import "components/attachment";
1516
@import "components/attachment-link";
1617
@import "components/back-link";
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@import "govuk_publishing_components/individual_component_support";
2+
3+
.gem-c-app-promo-banner {
4+
display: none;
5+
padding: govuk-spacing(5) 0;
6+
border-top: 4px solid $govuk-brand-colour;
7+
background-color: $govuk-rebrand-template-background-colour;
8+
@include govuk-text-colour;
9+
}
10+
11+
.gem-c-app-promo-banner--visible {
12+
display: block;
13+
}
14+
15+
.gem-c-app-promo-banner__container {
16+
display: flex;
17+
gap: govuk-spacing(3);
18+
align-items: center;
19+
}
20+
21+
.gem-c-app-promo-banner__icon {
22+
flex: 0 1 64px;
23+
min-width: 60px;
24+
height: auto;
25+
}
26+
27+
.gem-c-app-promo-banner__content {
28+
flex: 1 1 auto;
29+
display: flex;
30+
align-items: center;
31+
justify-content: space-between;
32+
flex-wrap: wrap;
33+
row-gap: govuk-spacing(1);
34+
column-gap: govuk-spacing(3);
35+
36+
// Ensures the view link appears beneath the text content
37+
// on larger screen sizes
38+
@include govuk-media-query($from: tablet) {
39+
flex-direction: column;
40+
align-items: start;
41+
}
42+
}
43+
44+
.gem-c-app-promo-banner__text {
45+
text-wrap-style: balance;
46+
}
47+
48+
.gem-c-app-promo-banner__close-button {
49+
border: 0;
50+
padding: 0;
51+
color: govuk-tint(govuk-colour("black"), 25%);
52+
background: none;
53+
flex: 0 0 24px;
54+
height: 24px;
55+
56+
&:hover {
57+
cursor: pointer;
58+
box-shadow: 0 4px govuk-colour("black");
59+
}
60+
61+
&:focus {
62+
@include govuk-focused-text;
63+
}
64+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<%
2+
add_gem_component_stylesheet("app_promo_banner")
3+
4+
disable_ga4 ||= false
5+
6+
component_helper = GovukPublishingComponents::Presenters::ComponentWrapperHelper.new(local_assigns)
7+
component_helper.add_class("gem-c-app-promo-banner")
8+
component_helper.add_role("region")
9+
component_helper.add_aria_attribute({ label: t("components.app_promo_banner.aria_label")})
10+
component_helper.set_hidden("hidden")
11+
component_helper.add_data_attribute({ module: "app-promo-banner", nosnippet: "" })
12+
component_helper.add_data_attribute({ ga4_app_promo_banner: "" }) unless disable_ga4 # Added to the parent element for the GA4 pageview object to use
13+
14+
ga4_event_attributes = {
15+
event_name: "select_content",
16+
type: "app promo banner",
17+
action: "closed",
18+
section: t("components.app_promo_banner.title_text"),
19+
text: t("components.app_promo_banner.close"),
20+
}.to_json unless disable_ga4
21+
22+
ga4_link_attributes = {
23+
event_name: "navigation",
24+
type: "app promo banner",
25+
index_link: 1,
26+
index_total: 1,
27+
}.to_json unless disable_ga4
28+
%>
29+
30+
<%= tag.div(**component_helper.all_attributes) do %>
31+
<%= tag.div(class: "govuk-width-container") do %>
32+
<%= tag.div(class: "gem-c-app-promo-banner__container") do %>
33+
<%= content_tag(:button, {
34+
class: "gem-c-app-promo-banner__close-button js-close-app-promo-banner",
35+
aria: {
36+
label: t("components.app_promo_banner.close"),
37+
},
38+
data: {
39+
module: disable_ga4 ? nil : "ga4-event-tracker",
40+
ga4_event: ga4_event_attributes,
41+
},
42+
}) do %>
43+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" focusable="false">
44+
<circle cx="12" cy="12" r="11.5" stroke="currentColor" />
45+
<path stroke="currentColor" stroke-linecap="round" stroke-width="1.5" d="m7 7 10 10m0-10L7 17" />
46+
</svg>
47+
<% end %>
48+
<%= image_tag((asset_path "govuk_publishing_components/app-promo-banner/govuk-app-icon.png"),
49+
alt: "",
50+
class: "gem-c-app-promo-banner__icon",
51+
width: "64",
52+
height: "64",
53+
) %>
54+
<%= tag.div(class: "gem-c-app-promo-banner__content") do %>
55+
<%= tag.div(class: "gem-c-app-promo-banner__text") do %>
56+
<%= tag.h2 t("components.app_promo_banner.title_text"), class: "govuk-heading-s govuk-!-margin-bottom-0" %>
57+
<%= tag.p t("components.app_promo_banner.body_text"), class: "govuk-body-s govuk-!-margin-bottom-0" %>
58+
<% end %>
59+
<%= link_to("https://play.google.com/store/apps/details?id=uk.gov.govuk&hl=en_GB",
60+
class: "govuk-link govuk-body govuk-!-margin-bottom-0",
61+
data: {
62+
module: disable_ga4 ? nil : "ga4-link-tracker",
63+
ga4_link: ga4_link_attributes,
64+
},
65+
) do %>
66+
<%= t("components.app_promo_banner.view") %>
67+
<%= tag.span("on Google Play", class: "govuk-visually-hidden") %>
68+
<% end %>
69+
<% end %>
70+
<% end %>
71+
<% end %>
72+
<% end %>

app/views/govuk_publishing_components/components/_layout_for_public.html.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
omit_header ||= false
1616
product_name ||= nil
1717
service_name ||= nil
18+
show_app_promo_banner ||= false
1819
show_cross_service_header ||= false
1920
draft_watermark ||= false
2021
title ||= "GOV.UK - The best place to find government services and information"
@@ -74,6 +75,9 @@
7475
<%= render "govuk_publishing_components/components/skip_link", {
7576
href: "#content",
7677
} %>
78+
<% if show_app_promo_banner %>
79+
<%= render "govuk_publishing_components/components/app_promo_banner" %>
80+
<% end %>
7781
<% end %>
7882
<% unless omit_header %>
7983
<% if show_cross_service_header %>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: App Promo Banner
2+
description: Displays a banner on Android devices to promote the GOVUK Android App
3+
body: |
4+
By default the app promo banner is hidden. The banner is only displayed if JavaScript is enabled and if the device is Android.
5+
6+
If the user has provided permission to use the settings cookie in their cookie preferences, then `app_promo_banner` cookie is set which is used to stop the banner reappearing once it has been closed.
7+
8+
accessibility_criteria: |
9+
- Adequate colour contrast between text, icons, and background meeting WCAG 2.2 AA as a minimum
10+
- Text does not overlap, truncate, or disappear when zoomed
11+
- “Skip to main” link precedes banner in DOM order and tab sequence
12+
- Close button can be activated with Enter/Space keys
13+
- Close button has an accessible name
14+
- View link is descriptive indicating where it goes to
15+
- Banner is reachable via keyboard navigation
16+
- Banner placement does not obscure other critical content or controls
17+
- Banner can be dismissed without requiring precise pointer actions
18+
- Banner content is understandable out of context
19+
- No keyboard trap: focus moves smoothly past the banner once dismissed
20+
shared_accessibility_criteria:
21+
- link
22+
uses_component_wrapper_helper: true
23+
examples:
24+
default:
25+
data:
26+
without_ga4_tracking:
27+
description: |
28+
Disables GA4 tracking on the app promo banner. Tracking is enabled by default. This adds a data module and data-attributes with JSONs to the accordion. See the [ga4-event-tracker documentation](https://github.com/alphagov/govuk_publishing_components/blob/main/docs/analytics-ga4/ga4-event-tracker.md) for more information.
29+
data:
30+
disable_ga4: true

0 commit comments

Comments
 (0)