diff --git a/CHANGELOG.md b/CHANGELOG.md index ce853edda3..91591b8443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +:new: **New features** + +#### New notification banner component + +We’ve added a new notification banner component, ported from the GOV.UK Design System. + +This was added in [pull request #1408: Add notification banner component](https://github.com/nhsuk/nhsuk-frontend/pull/1408). + :boom: **Breaking changes** You must make the following changes when you migrate to this release, or your service might break. diff --git a/packages/nhsuk-frontend-review/src/examples/all.njk b/packages/nhsuk-frontend-review/src/examples/all.njk index 2224c90039..c1c705f554 100644 --- a/packages/nhsuk-frontend-review/src/examples/all.njk +++ b/packages/nhsuk-frontend-review/src/examples/all.njk @@ -50,6 +50,13 @@ {% block content %}
+ {% call notificationBanner() %} +

+ You have 7 days left to send your application. + View application. +

+ {% endcall %} +

Components

{{ contentsList({ diff --git a/packages/nhsuk-frontend-review/src/layouts/page.njk b/packages/nhsuk-frontend-review/src/layouts/page.njk index f438c4a4f3..add30db15c 100644 --- a/packages/nhsuk-frontend-review/src/layouts/page.njk +++ b/packages/nhsuk-frontend-review/src/layouts/page.njk @@ -20,6 +20,7 @@ {% from 'nhsuk/components/input/macro.njk' import input %} {% from 'nhsuk/components/inset-text/macro.njk' import insetText %} {% from 'nhsuk/components/label/macro.njk' import label %} +{% from 'nhsuk/components/notification-banner/macro.njk' import notificationBanner %} {% from 'nhsuk/components/pagination/macro.njk' import pagination %} {% from 'nhsuk/components/panel/macro.njk' import panel %} {% from 'nhsuk/components/radios/macro.njk' import radios %} diff --git a/packages/nhsuk-frontend/src/nhsuk/components/_index.scss b/packages/nhsuk-frontend/src/nhsuk/components/_index.scss index b3d5729082..e37a751f95 100644 --- a/packages/nhsuk-frontend/src/nhsuk/components/_index.scss +++ b/packages/nhsuk-frontend/src/nhsuk/components/_index.scss @@ -22,6 +22,7 @@ @forward "input"; @forward "inset-text"; @forward "label"; +@forward "notification-banner"; @forward "pagination"; @forward "panel"; @forward "checkboxes"; diff --git a/packages/nhsuk-frontend/src/nhsuk/components/index.mjs b/packages/nhsuk-frontend/src/nhsuk/components/index.mjs index 69985dd361..ac60ce2401 100644 --- a/packages/nhsuk-frontend/src/nhsuk/components/index.mjs +++ b/packages/nhsuk-frontend/src/nhsuk/components/index.mjs @@ -3,6 +3,7 @@ export * from './character-count/character-count.mjs' export * from './checkboxes/checkboxes.mjs' export * from './error-summary/error-summary.mjs' export * from './header/header.mjs' +export * from './notification-banner/notification-banner.mjs' export * from './radios/radios.mjs' export * from './skip-link/skip-link.mjs' export * from './tabs/tabs.mjs' diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/README.md b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/README.md new file mode 100644 index 0000000000..b358a5034a --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/README.md @@ -0,0 +1,9 @@ +# Notification banner + +## Installation + +See the [main README quick start guide](https://github.com/nhsuk/nhsuk-frontend#quick-start) for how to install this component. + +## Guidance and examples + +To learn more about the notification banner component and when to use it, visit the [design system in the NHS digital service manual](https://service-manual.nhs.uk/design-system/components/notification-banner) for guidance, examples and options. diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/_index.scss b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/_index.scss new file mode 100644 index 0000000000..7b0d519b1f --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/_index.scss @@ -0,0 +1,94 @@ +@use "../../core/settings" as *; +@use "../../core/tools" as *; + +.nhsuk-notification-banner { + border: $nhsuk-border-width solid $color_nhsuk-blue; + + @include nhsuk-font($size: 19); + @include nhsuk-responsive-margin(8, "bottom"); + + &:focus { + outline: $nhsuk-focus-width solid $nhsuk-focus-color; + } +} + +.nhsuk-notification-banner__header { + padding: 2px nhsuk-spacing(3) nhsuk-spacing(1); + background-color: $color_nhsuk-blue; + + // Ensures the notification header appears separate to the notification body + // text in high contrast mode + border-bottom: 1px solid transparent; + + @include nhsuk-media-query($from: tablet) { + padding: 2px nhsuk-spacing(4) nhsuk-spacing(1); + } +} + +.nhsuk-notification-banner__title { + margin: 0; + padding: 0; + color: $color_nhsuk-white; + + // Set the size again because this element is a heading and the user agent + // font size overrides the inherited font size + @include nhsuk-font($size: 19, $weight: bold); +} + +.nhsuk-notification-banner__content { + $padding-tablet: nhsuk-spacing(4); + padding: nhsuk-spacing(3); + + @include nhsuk-text-color; + @include nhsuk-media-query($from: tablet) { + padding: $padding-tablet; + } + + // Wrap content at the same place that a 2/3 grid column ends, to maintain + // shorter line-lengths when the notification banner is full width + > * { + // When elements have their own padding (like lists), include the padding + // in the max-width calculation + box-sizing: border-box; + + // Calculate the internal width of a two-thirds column... + $two-col-width: calc($nhsuk-page-width * 2 / 3) - calc($nhsuk-gutter * 1 / 3); + + // ...and then factor in the left border and padding + $banner-exterior: ($padding-tablet + $nhsuk-border-width); + max-width: $two-col-width - $banner-exterior; + } + + > :last-child { + margin-bottom: 0; + } +} + +.nhsuk-notification-banner__heading { + margin: 0 0 nhsuk-spacing(3); + padding: 0; + + @include nhsuk-font($size: 26, $weight: bold); +} + +.nhsuk-notification-banner__link { + @include nhsuk-link-style-no-visited-state; +} + +.nhsuk-notification-banner--success { + border-color: $nhsuk-button-color; + + .nhsuk-notification-banner__header { + background-color: $nhsuk-button-color; + } + + .nhsuk-notification-banner__link { + @include nhsuk-link-style-success; + @include nhsuk-link-style( + $link-color: $nhsuk-button-color, + $link-visited-color: $nhsuk-button-color, + $link-hover-color: $nhsuk-button-hover-color, + $link-active-color: $nhsuk-button-active-color + ); + } +} diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/_notification-banner.scss b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/_notification-banner.scss new file mode 100644 index 0000000000..0c7e92f6f8 --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/_notification-banner.scss @@ -0,0 +1 @@ +@forward "."; diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/accessibility.puppeteer.test.mjs b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/accessibility.puppeteer.test.mjs new file mode 100644 index 0000000000..4bb8e8e67f --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/accessibility.puppeteer.test.mjs @@ -0,0 +1,35 @@ +import { axe, goToComponent } from '@nhsuk/frontend-helpers/puppeteer.mjs' + +import { examples } from './macro-options.mjs' + +describe('Notification banner', () => { + /** @type {RuleObject} */ + let axeRules + + /** @type {Page} */ + let page + + beforeAll(() => { + axeRules = { + /** + * Ignore 'Element has a tabindex greater than 0' for custom + * tabindex tests + */ + tabindex: { enabled: false } + } + }) + + describe('Component examples', () => { + it('passes accessibility tests', async () => { + for (const example in examples) { + page = await goToComponent(browser, 'notification-banner', { example }) + await expect(axe(page, axeRules)).resolves.toHaveNoViolations() + } + }, 120000) + }) +}) + +/** + * @import { RuleObject } from 'axe-core' + * @import { Page } from 'puppeteer' + */ diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/macro-options.mjs b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/macro-options.mjs new file mode 100644 index 0000000000..d0c47d0626 --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/macro-options.mjs @@ -0,0 +1,209 @@ +import { components } from '@nhsuk/frontend-lib' +import { outdent } from 'outdent' + +export const name = 'Notification banner' + +/** + * Nunjucks macro option params + * + * @satisfies {{ [param: string]: MacroParam }} + */ +export const params = { + text: { + type: 'string', + required: true, + description: + 'The text that displays in the notification banner. You can use any string with this option. If you set `html`, this option is not required and is ignored.' + }, + html: { + type: 'string', + required: true, + description: + 'The HTML to use within the notification banner. You can use any string with this option. If you set `html`, `text` is not required and is ignored.' + }, + caller: { + type: 'nunjucks-block', + required: false, + description: + 'Not strictly a parameter but [Nunjucks code convention](https://mozilla.github.io/nunjucks/templating.html#call). Using a `call` block enables you to call a macro with all the text inside the tag. This is helpful if you want to pass a lot of content into a macro. To use it, you will need to wrap the entire notification banner component in a `call` block.' + }, + titleText: { + type: 'string', + required: false, + description: outdent` + The title text that displays in the notification banner. You can use any string with this option. Use this option to set text that does not contain HTML. The available default values are \'Important\', \'Success\', and null: + - if you do not set \`type\`, \`titleText\` defaults to \`"Important"\` + - if you set \`type\` to \`success\`, \`titleText\` defaults to \`"Success"\` + - if you set \`titleHtml\`, this option is ignored + ` + }, + titleHtml: { + type: 'string', + required: false, + description: + 'The title HTML to use within the notification banner. You can use any string with this option. Use this option to set text that contains HTML. If you set `titleHtml`, the `titleText` option is ignored.' + }, + titleHeadingLevel: { + type: 'string', + required: false, + description: + 'Sets heading level for the title only. You can only use values between `1` and `6` with this option. The default is `2`.' + }, + type: { + type: 'string', + required: false, + description: + 'The type of notification to render. You can use only `"success"` or `null` values with this option. If you set `type` to `"success"`, the notification banner sets `role` to `"alert"`. JavaScript then moves the keyboard focus to the notification banner when the page loads. If you do not set `type`, the notification banner sets `role` to `"region"`.' + }, + role: { + type: 'string', + required: false, + description: + 'Overrides the value of the `role` attribute for the notification banner. Defaults to `"region"`. If you set `type` to `"success"`, `role` defaults to `"alert"`.' + }, + titleId: { + type: 'string', + required: false, + description: + 'The `id` for the banner title, and the `aria-labelledby` attribute in the banner. Defaults to `"nhsuk-notification-banner-title"`.' + }, + disableAutoFocus: { + type: 'boolean', + required: false, + description: + 'If you set `type` to `"success"`, or `role` to `"alert"`, JavaScript moves the keyboard focus to the notification banner when the page loads. To disable this behaviour, set `disableAutoFocus` to `true`.' + }, + classes: { + type: 'string', + required: false, + description: 'The classes that you want to add to the notification banner.' + }, + attributes: { + type: 'object', + required: false, + description: + 'The HTML attributes that you want to add to the notification banner, for example, data attributes.' + } +} + +/** + * Nunjucks macro option examples + * + * @satisfies {{ [example: string]: MacroExample }} + */ +export const examples = { + 'default': { + context: { + text: 'The patient record was updated.' + }, + screenshot: true + }, + 'paragraph as html heading': { + context: { + html: '

You have 9 days to send a response.

' + } + }, + 'with text as html': { + context: { + html: outdent` +

+ The patient record was updated +

+

+ Contact example@nhs.uk if you think there's a problem. +

+ ` + } + }, + 'with type as success': { + context: { + type: 'success', + text: 'Email sent to example@email.com' + }, + screenshot: true + }, + 'success with custom html': { + context: { + type: 'success', + html: outdent` +

+ 4 files uploaded +

+ + ` + } + }, + 'with a list': { + context: { + html: outdent` +

4 files uploaded

+ + ` + } + }, + 'with long heading': { + context: { + text: 'The patient record was withdrawn on 7 March 2014, before being sent in, sent back, queried, lost, found, subjected to public inquiry, lost again, and finally buried in soft peat for three months and recycled as firelighters.' + } + }, + 'with lots of content': { + context: { + html: outdent` +

+ Check if you need to apply the reverse charge to this application +

+

+ You will have to apply the reverse charge if the applicant supplies any of these services: +

+ + ` + } + }, + 'auto-focus disabled, with type as success': { + context: { + type: 'success', + disableAutoFocus: true, + text: 'Email sent to example@email.com' + } + }, + 'auto-focus explicitly enabled, with type as success': { + context: { + type: 'success', + disableAutoFocus: false, + text: 'Email sent to example@email.com' + } + }, + 'role=alert overridden to role=region, with type as success': { + context: { + type: 'success', + role: 'region', + text: 'Email sent to example@email.com' + } + }, + 'custom tabindex': { + context: { + type: 'success', + text: 'Email sent to example@email.com', + attributes: { + tabindex: 2 + } + } + } +} + +export const options = components.getMacroOptions(params) + +/** + * @import { MacroExample, MacroParam } from '@nhsuk/frontend-lib/components.mjs' + */ diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/macro.njk b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/macro.njk new file mode 100644 index 0000000000..d1d2062c4f --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/macro.njk @@ -0,0 +1,3 @@ +{% macro notificationBanner(params) %} + {%- include "./template.njk" -%} +{% endmacro %} diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.jsdom.test.mjs b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.jsdom.test.mjs new file mode 100644 index 0000000000..25b289f103 --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.jsdom.test.mjs @@ -0,0 +1,84 @@ +import { components } from '@nhsuk/frontend-lib' + +import { examples } from './macro-options.mjs' +import { + NotificationBanner, + initNotificationBanners +} from './notification-banner.mjs' + +describe('Notification banner', () => { + /** @type {HTMLElement} */ + let $root + + beforeEach(() => { + document.body.innerHTML = components.render( + 'notification-banner', + examples['with type as success'] + ) + + $root = /** @type {HTMLElement} */ ( + document.querySelector(`[data-module="${NotificationBanner.moduleName}"]`) + ) + }) + + describe('Initialisation via init function', () => { + it('should not throw with missing notification banner', () => { + $root.remove() + expect(() => initNotificationBanners()).not.toThrow() + }) + + it('should not throw with empty body', () => { + document.body.innerHTML = '' + expect(() => initNotificationBanners()).not.toThrow() + }) + + it('should not throw with empty scope', () => { + const scope = document.createElement('div') + expect(() => initNotificationBanners({ scope })).not.toThrow() + }) + }) + + describe('Initialisation via class', () => { + it('should not throw with $root element', () => { + expect(() => new NotificationBanner($root)).not.toThrow() + }) + + it('should throw with unsupported browser', () => { + document.body.classList.remove('nhsuk-frontend-supported') + + expect(() => new NotificationBanner($root)).toThrow( + 'NHS.UK frontend is not supported in this browser' + ) + }) + + it('should throw with missing $root element', () => { + expect(() => new NotificationBanner()).toThrow( + `${NotificationBanner.moduleName}: Root element (\`$root\`) not found` + ) + }) + + it('should throw with wrong $root element type', () => { + const $svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + + expect(() => new NotificationBanner($svg)).toThrow( + `${NotificationBanner.moduleName}: Root element (\`$root\`) is not of type HTMLElement` + ) + }) + + it('should throw when initialised twice', () => { + expect(() => { + new NotificationBanner($root) + new NotificationBanner($root) + }).toThrow( + `${NotificationBanner.moduleName}: Root element (\`$root\`) already initialised` + ) + }) + }) + + describe('Accessibility', () => { + it('should add accessible name and role', () => { + expect($root).toHaveAccessibleName('Success') + expect($root).toHaveRole('alert') + }) + }) +}) diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.mjs b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.mjs new file mode 100644 index 0000000000..609acf0f0a --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.mjs @@ -0,0 +1,105 @@ +import { setFocus } from '../../common/index.mjs' +import { Component } from '../../component.mjs' + +/** + * Notification banner component + * + * Adapted from https://github.com/alphagov/govuk-frontend/blob/v5.10.2/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.mjs + */ +export class NotificationBanner extends Component { + /** + * @param {Element | null} [$root] - HTML element to use for notification banner + */ + constructor($root) { + super($root) + + /** + * Read config set using dataset ('data-' values) + * + * @type {NotificationBannerConfig} + */ + this.config = Object.assign( + {}, + NotificationBanner.defaults, + NotificationBanner.getDataset(this.$root) + ) + + /** + * Focus the notification banner + * + * If `role="alert"` is set, focus the element to help some assistive + * technologies prioritise announcing it. + * + * You can turn off the auto-focus functionality by setting + * `data-disable-auto-focus="true"` in the component HTML. You might wish to + * do this based on user research findings, or to avoid a clash with another + * element which should be focused when the page loads. + */ + if ( + this.$root.getAttribute('role') === 'alert' && + !this.config.disableAutoFocus + ) { + setFocus(this.$root) + } + } + + /** + * Read data attributes + * + * @param {HTMLElement} element - HTML element + */ + static getDataset(element) { + const dataset = /** @type {NotificationBannerConfig} */ ({}) + + for (const [key, value] of Object.entries(element.dataset)) { + if (key === 'disableAutoFocus') { + dataset[key] = value === 'true' + } + } + + return dataset + } + + /** + * Name for the component used when initialising using data-module attributes. + */ + static moduleName = 'nhsuk-notification-banner' + + /** + * Notification banner default config + * + * @see {@link NotificationBannerConfig} + * @constant + * @type {NotificationBannerConfig} + */ + static defaults = Object.freeze({ + disableAutoFocus: false + }) +} + +/** + * Initialise notification banner component + * + * @param {object} [options] + * @param {Element | Document | null} [options.scope] - Scope of the document to search within + */ +export function initNotificationBanners(options = {}) { + const $scope = options.scope ?? document + const $notificationBanners = $scope.querySelectorAll( + `[data-module="${NotificationBanner.moduleName}"]` + ) + + $notificationBanners.forEach(($notificationBanner) => { + new NotificationBanner($notificationBanner) + }) +} + +/** + * Notification banner config + * + * @typedef {object} NotificationBannerConfig + * @property {boolean} [disableAutoFocus=false] - If set to `true` the + * notification banner will not be focussed when the page loads. This only + * applies if the component has a `role` of `alert` – in other cases the + * component will not be focused on page load, regardless of this option. + */ diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.puppeteer.test.mjs b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.puppeteer.test.mjs new file mode 100644 index 0000000000..6522676a87 --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/notification-banner.puppeteer.test.mjs @@ -0,0 +1,114 @@ +import { goToComponent } from '@nhsuk/frontend-helpers/puppeteer.mjs' + +describe('Notification banner', () => { + /** @type {Page} */ + let page + + describe('when type is set to "success"', () => { + beforeAll(async () => { + page = await goToComponent(browser, 'notification-banner', { + example: 'with type as success' + }) + }) + + it('has the correct tabindex attribute to be focused with JavaScript', async () => { + const tabindex = await page.$eval('.nhsuk-notification-banner', (el) => + el.getAttribute('tabindex') + ) + + expect(tabindex).toBe('-1') + }) + + it('is automatically focused when the page loads', async () => { + const activeElement = await page.evaluate(() => + document.activeElement.getAttribute('data-module') + ) + + expect(activeElement).toBe('nhsuk-notification-banner') + }) + + it('removes the tabindex attribute on blur', async () => { + await page.$eval( + '.nhsuk-notification-banner', + (el) => el instanceof window.HTMLElement && el.blur() + ) + + const tabindex = await page.$eval('.nhsuk-notification-banner', (el) => + el.getAttribute('tabindex') + ) + expect(tabindex).toBeNull() + }) + + describe('and auto-focus is disabled using data attributes', () => { + beforeAll(async () => { + page = await goToComponent(browser, 'notification-banner', { + example: 'auto-focus disabled, with type as success' + }) + }) + + it('does not have a tabindex attribute', async () => { + const tabindex = await page.$eval('.nhsuk-notification-banner', (el) => + el.getAttribute('tabindex') + ) + + expect(tabindex).toBeNull() + }) + + it('does not focus the notification banner', async () => { + const activeElement = await page.evaluate(() => + document.activeElement.getAttribute('data-module') + ) + + expect(activeElement).not.toBe('nhsuk-notification-banner') + }) + }) + + describe('and role is overridden to "region"', () => { + beforeAll(async () => { + page = await goToComponent(browser, 'notification-banner', { + example: 'role=alert overridden to role=region, with type as success' + }) + }) + + it('does not have a tabindex attribute', async () => { + const tabindex = await page.$eval('.nhsuk-notification-banner', (el) => + el.getAttribute('tabindex') + ) + + expect(tabindex).toBeNull() + }) + + it('does not focus the notification banner', async () => { + const activeElement = await page.evaluate(() => + document.activeElement.getAttribute('data-module') + ) + + expect(activeElement).not.toBe('nhsuk-notification-banner') + }) + }) + + describe('and a custom tabindex is set', () => { + beforeAll(async () => { + page = await goToComponent(browser, 'notification-banner', { + example: 'custom tabindex' + }) + }) + + it('does not remove the tabindex attribute on blur', async () => { + await page.$eval( + '.nhsuk-notification-banner', + (el) => el instanceof window.HTMLElement && el.blur() + ) + + const tabindex = await page.$eval('.nhsuk-notification-banner', (el) => + el.getAttribute('tabindex') + ) + expect(tabindex).toBe('2') + }) + }) + }) +}) + +/** + * @import { Page } from 'puppeteer' + */ diff --git a/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/template.njk b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/template.njk new file mode 100644 index 0000000000..748d0bf9e1 --- /dev/null +++ b/packages/nhsuk-frontend/src/nhsuk/components/notification-banner/template.njk @@ -0,0 +1,49 @@ +{% from "nhsuk/macros/attributes.njk" import nhsukAttributes %} + +{%- if params.type == "success" %} + {% set successBanner = true %} +{% endif %} + +{%- if successBanner %} + {% set typeClass = "nhsuk-notification-banner--" + params.type %} +{% endif %} + +{%- if params.role %} + {% set role = params.role %} +{% elif successBanner %} + {#- If type is success, add `role="alert"` to prioritise the information in the notification banner to users of assistive technologies -#} + {% set role = "alert" %} +{% else %} + {#- Otherwise add `role="region"` to make the notification banner a landmark to help users of assistive technologies to navigate to the banner -#} + {% set role = "region" %} +{% endif %} + +{%- if params.titleHtml %} + {% set title = params.titleHtml | safe %} +{%- elif params.titleText %} + {% set title = params.titleText %} +{%- elif successBanner %} + {% set title = "Success" %} +{%- else %} + {% set title = "Important" %} +{%- endif -%} + +
+
+ + {{ title }} + +
+
+ {% if caller or params.html %} + {{ caller() if caller else params.html | safe | trim | indent(4) }} + {% elif params.text %} + {# Set default style for single line content -#} +

+ {{ params.text | trim | indent(6) }} +

+ {% endif %} +
+
diff --git a/packages/nhsuk-frontend/src/nhsuk/core/tools/_links.scss b/packages/nhsuk-frontend/src/nhsuk/core/tools/_links.scss index b9ffecc456..cb1b468a70 100644 --- a/packages/nhsuk-frontend/src/nhsuk/core/tools/_links.scss +++ b/packages/nhsuk-frontend/src/nhsuk/core/tools/_links.scss @@ -132,6 +132,30 @@ } } +/// Success link styles +/// +/// Makes links use the success colour. The link will darken if it's active or a +/// user hovers their cursor over it. +/// +/// If you use this mixin in a component you must also include the +/// nhsuk-link-style-default mixin in order to get the focus state. +/// +/// @example scss +/// .nhsuk-component__link { +/// @include nhsuk-link-style-default; +/// @include nhsuk-link-style-success; +/// } +/// + +@mixin nhsuk-link-style-success { + @include nhsuk-link-style( + $link-color: $nhsuk-button-color, + $link-visited-color: $nhsuk-button-color, + $link-hover-color: $nhsuk-button-hover-color, + $link-active-color: $nhsuk-button-active-color + ); +} + /// No visited state link mixin /// /// Used in cases where it is not helpful to distinguish between visited and diff --git a/packages/nhsuk-frontend/src/nhsuk/index.jsdom.test.mjs b/packages/nhsuk-frontend/src/nhsuk/index.jsdom.test.mjs index 8477a98e7e..5908f022fe 100644 --- a/packages/nhsuk-frontend/src/nhsuk/index.jsdom.test.mjs +++ b/packages/nhsuk-frontend/src/nhsuk/index.jsdom.test.mjs @@ -5,6 +5,7 @@ import { initCheckboxes, initErrorSummary, initHeader, + initNotificationBanners, initRadios, initSkipLinks, initTabs @@ -16,6 +17,7 @@ jest.mock('./components/character-count/character-count.mjs') jest.mock('./components/checkboxes/checkboxes.mjs') jest.mock('./components/error-summary/error-summary.mjs') jest.mock('./components/header/header.mjs') +jest.mock('./components/notification-banner/notification-banner.mjs') jest.mock('./components/radios/radios.mjs') jest.mock('./components/skip-link/skip-link.mjs') jest.mock('./components/tabs/tabs.mjs') @@ -36,6 +38,7 @@ describe('NHS.UK frontend', () => { expect(NHSUKFrontend).toHaveProperty('initCheckboxes') expect(NHSUKFrontend).toHaveProperty('initErrorSummary') expect(NHSUKFrontend).toHaveProperty('initHeader') + expect(NHSUKFrontend).toHaveProperty('initNotificationBanners') expect(NHSUKFrontend).toHaveProperty('initRadios') expect(NHSUKFrontend).toHaveProperty('initSkipLinks') expect(NHSUKFrontend).toHaveProperty('initTabs') @@ -59,6 +62,7 @@ describe('NHS.UK frontend', () => { expect(initCheckboxes).not.toHaveBeenCalled() expect(initErrorSummary).not.toHaveBeenCalled() expect(initHeader).not.toHaveBeenCalled() + expect(initNotificationBanners).not.toHaveBeenCalled() expect(initRadios).not.toHaveBeenCalled() expect(initSkipLinks).not.toHaveBeenCalled() expect(initTabs).not.toHaveBeenCalled() @@ -72,6 +76,7 @@ describe('NHS.UK frontend', () => { expect(initCheckboxes).toHaveBeenCalled() expect(initErrorSummary).toHaveBeenCalled() expect(initHeader).toHaveBeenCalled() + expect(initNotificationBanners).toHaveBeenCalled() expect(initRadios).toHaveBeenCalled() expect(initSkipLinks).toHaveBeenCalled() expect(initTabs).toHaveBeenCalled() @@ -87,6 +92,7 @@ describe('NHS.UK frontend', () => { expect(initCheckboxes).toHaveBeenCalledWith({ scope }) expect(initErrorSummary).toHaveBeenCalledWith({ scope }) expect(initHeader).toHaveBeenCalledWith({ scope }) + expect(initNotificationBanners).toHaveBeenCalledWith({ scope }) expect(initRadios).toHaveBeenCalledWith({ scope }) expect(initSkipLinks).toHaveBeenCalledWith({ scope }) expect(initTabs).toHaveBeenCalledWith({ scope }) diff --git a/packages/nhsuk-frontend/src/nhsuk/index.mjs b/packages/nhsuk-frontend/src/nhsuk/index.mjs index f79ea2acea..153f52d8e3 100644 --- a/packages/nhsuk-frontend/src/nhsuk/index.mjs +++ b/packages/nhsuk-frontend/src/nhsuk/index.mjs @@ -6,6 +6,7 @@ import { initCharacterCounts, initCheckboxes, initErrorSummary, + initNotificationBanners, initSkipLinks, initTabs } from './components/index.mjs' @@ -36,6 +37,7 @@ export function initAll($scope) { initCharacterCounts(options) initCheckboxes(options) initErrorSummary(options) + initNotificationBanners(options) initRadios(options) initTabs(options) } diff --git a/tests/backstop/bitmaps_reference/Notification_banner_desktop.png b/tests/backstop/bitmaps_reference/Notification_banner_desktop.png new file mode 100644 index 0000000000..3b290d71d3 Binary files /dev/null and b/tests/backstop/bitmaps_reference/Notification_banner_desktop.png differ diff --git a/tests/backstop/bitmaps_reference/Notification_banner_mobile.png b/tests/backstop/bitmaps_reference/Notification_banner_mobile.png new file mode 100644 index 0000000000..6dd0c16a4b Binary files /dev/null and b/tests/backstop/bitmaps_reference/Notification_banner_mobile.png differ diff --git a/tests/backstop/bitmaps_reference/Notification_banner_with_type_as_success_desktop.png b/tests/backstop/bitmaps_reference/Notification_banner_with_type_as_success_desktop.png new file mode 100644 index 0000000000..b4aace1d33 Binary files /dev/null and b/tests/backstop/bitmaps_reference/Notification_banner_with_type_as_success_desktop.png differ diff --git a/tests/backstop/bitmaps_reference/Notification_banner_with_type_as_success_mobile.png b/tests/backstop/bitmaps_reference/Notification_banner_with_type_as_success_mobile.png new file mode 100644 index 0000000000..436db09efd Binary files /dev/null and b/tests/backstop/bitmaps_reference/Notification_banner_with_type_as_success_mobile.png differ