diff --git a/src/client/stylesheets/_prose.scss b/src/client/stylesheets/_prose.scss index a929dceba..ec7801fe7 100644 --- a/src/client/stylesheets/_prose.scss +++ b/src/client/stylesheets/_prose.scss @@ -26,6 +26,10 @@ @extend %govuk-body-m; } + > :last-child { + margin-bottom: 0; + } + strong, b { @include govuk-typography-weight-bold; diff --git a/src/server/forms/simple-form.yaml b/src/server/forms/simple-form.yaml index efa8f8215..74d00a8a0 100644 --- a/src/server/forms/simple-form.yaml +++ b/src/server/forms/simple-form.yaml @@ -69,6 +69,25 @@ pages: id: '00738799-3489-4ab2-a57b-542eecb31bfa' next: [] id: da0fbdb4-a2de-4650-be16-9ba552af135f + - title: '' + path: '/notification-demo' + components: + - type: NotificationBanner + title: Important + name: notificationImportant + content: 'Contact us if you need [help with your application](https://www.defra.gov.uk).' + options: + heading: There may be a delay in processing your application. + id: ca6b6590-1608-49f4-82f9-9d92e38e66ea + - type: NotificationBanner + title: Success + name: notificationSuccess + content: 'Your application has been submitted.' + options: + type: success + id: b6960f4b-d771-4956-9c9b-17da5c603062 + next: [] + id: be4b4b86-514c-43bb-8cdc-f9aed6155924 - id: 449a45f6-4541-4a46-91bd-8b8931b07b50 title: '' path: '/summary' diff --git a/src/server/plugins/engine/components/NotificationBanner.test.ts b/src/server/plugins/engine/components/NotificationBanner.test.ts new file mode 100644 index 000000000..de8efd586 --- /dev/null +++ b/src/server/plugins/engine/components/NotificationBanner.test.ts @@ -0,0 +1,83 @@ +import { + ComponentType, + type NotificationBannerComponent +} from '@defra/forms-model' + +import { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js' +import { FormModel } from '~/src/server/plugins/engine/models/FormModel.js' +import definition from '~/test/form/definitions/basic.js' + +describe('NotificationBanner', () => { + let model: FormModel + + beforeEach(() => { + model = new FormModel(definition, { + basePath: 'test' + }) + }) + + it('sets Nunjucks component defaults', () => { + const def = { + title: 'Important', + name: 'myComponent', + type: ComponentType.NotificationBanner, + content: 'You have 30 days to [appeal this decision](/appeal).', + options: {} + } satisfies NotificationBannerComponent + + const collection = new ComponentCollection([def], { model }) + const viewModel = collection.guidance[0].getViewModel() + + expect(viewModel).toEqual( + expect.objectContaining({ + attributes: {}, + titleHtml: def.title, + content: def.content + }) + ) + }) + + it('includes heading in view model', () => { + const def = { + title: 'Important', + name: 'myComponent', + type: ComponentType.NotificationBanner, + content: 'Contact us if you need help.', + options: { + heading: 'There may be a **delay** in processing your application.' + } + } satisfies NotificationBannerComponent + + const collection = new ComponentCollection([def], { model }) + const viewModel = collection.guidance[0].getViewModel() + + expect(viewModel).toEqual( + expect.objectContaining({ + titleHtml: def.title, + content: def.content, + heading: def.options.heading + }) + ) + }) + + it('sets type: success for success variant', () => { + const def = { + title: 'Success', + name: 'myComponent', + type: ComponentType.NotificationBanner, + content: 'Your application has been submitted.', + options: { type: 'success' } + } satisfies NotificationBannerComponent + + const collection = new ComponentCollection([def], { model }) + const viewModel = collection.guidance[0].getViewModel() + + expect(viewModel).toEqual( + expect.objectContaining({ + titleHtml: def.title, + content: def.content, + type: 'success' + }) + ) + }) +}) diff --git a/src/server/plugins/engine/components/NotificationBanner.ts b/src/server/plugins/engine/components/NotificationBanner.ts new file mode 100644 index 000000000..2ee78cf3b --- /dev/null +++ b/src/server/plugins/engine/components/NotificationBanner.ts @@ -0,0 +1,32 @@ +import { type NotificationBannerComponent } from '@defra/forms-model' + +import { ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js' + +export class NotificationBanner extends ComponentBase { + declare options: NotificationBannerComponent['options'] + content: NotificationBannerComponent['content'] + + constructor( + def: NotificationBannerComponent, + props: ConstructorParameters[1] + ) { + super(def, props) + + const { content, options } = def + + this.content = content + this.options = options + } + + getViewModel() { + const { content, title, viewModel } = this + + return { + ...viewModel, + titleHtml: title, + content, + heading: this.options.heading, + type: this.options.type + } + } +} diff --git a/src/server/plugins/engine/components/helpers/components.ts b/src/server/plugins/engine/components/helpers/components.ts index 87e1cc264..fbda7be88 100644 --- a/src/server/plugins/engine/components/helpers/components.ts +++ b/src/server/plugins/engine/components/helpers/components.ts @@ -46,6 +46,7 @@ export type Guidance = | InstanceType | InstanceType | InstanceType + | InstanceType // List component instances only export type ListField = InstanceType< @@ -134,6 +135,10 @@ export function createComponent( component = new Components.Markdown(def, options) break + case ComponentType.NotificationBanner: + component = new Components.NotificationBanner(def, options) + break + case ComponentType.MultilineTextField: component = new Components.MultilineTextField(def, options) break diff --git a/src/server/plugins/engine/components/index.ts b/src/server/plugins/engine/components/index.ts index 7b59f6b11..8662e8ad2 100644 --- a/src/server/plugins/engine/components/index.ts +++ b/src/server/plugins/engine/components/index.ts @@ -15,6 +15,7 @@ export { Html } from '~/src/server/plugins/engine/components/Html.js' export { InsetText } from '~/src/server/plugins/engine/components/InsetText.js' export { List } from '~/src/server/plugins/engine/components/List.js' export { Markdown } from '~/src/server/plugins/engine/components/Markdown.js' +export { NotificationBanner } from '~/src/server/plugins/engine/components/NotificationBanner.js' export { MonthYearField } from '~/src/server/plugins/engine/components/MonthYearField.js' export { MultilineTextField } from '~/src/server/plugins/engine/components/MultilineTextField.js' export { NumberField } from '~/src/server/plugins/engine/components/NumberField.js' diff --git a/src/server/plugins/engine/views/components/notificationbanner.html b/src/server/plugins/engine/views/components/notificationbanner.html new file mode 100644 index 000000000..8aaa7fb78 --- /dev/null +++ b/src/server/plugins/engine/views/components/notificationbanner.html @@ -0,0 +1,15 @@ +{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} + +{% macro NotificationBanner(component) %} + {%- set bannerHtml -%} +
+ {%- if component.model.heading %}{{ component.model.heading | markdown | safe }}{% endif -%} + {{ component.model.content | markdown | safe }} +
+ {%- endset -%} + {{ govukNotificationBanner({ + titleHtml: component.model.titleHtml, + html: bannerHtml, + type: component.model.type + }) }} +{% endmacro %}