@defra/forms-engine-plugin 4.15.0 → 4.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/.public/stylesheets/application.min.css +1 -1
  2. package/.public/stylesheets/application.min.css.map +1 -1
  3. package/.server/client/stylesheets/_prose.scss +19 -0
  4. package/.server/server/forms/simple-form.yaml +19 -0
  5. package/.server/server/plugins/engine/components/DatePartsField.js +28 -4
  6. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  7. package/.server/server/plugins/engine/components/MonthYearField.js +23 -1
  8. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  9. package/.server/server/plugins/engine/components/NotificationBanner.d.ts +56 -0
  10. package/.server/server/plugins/engine/components/NotificationBanner.js +28 -0
  11. package/.server/server/plugins/engine/components/NotificationBanner.js.map +1 -0
  12. package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
  13. package/.server/server/plugins/engine/components/helpers/components.js +3 -0
  14. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  15. package/.server/server/plugins/engine/components/index.d.ts +1 -0
  16. package/.server/server/plugins/engine/components/index.js +1 -0
  17. package/.server/server/plugins/engine/components/index.js.map +1 -1
  18. package/.server/server/plugins/engine/views/components/notificationbanner.html +15 -0
  19. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  20. package/package.json +2 -2
  21. package/src/client/stylesheets/_prose.scss +19 -0
  22. package/src/server/forms/simple-form.yaml +19 -0
  23. package/src/server/plugins/engine/components/DatePartsField.ts +25 -5
  24. package/src/server/plugins/engine/components/MonthYearField.ts +29 -1
  25. package/src/server/plugins/engine/components/NotificationBanner.ts +32 -0
  26. package/src/server/plugins/engine/components/helpers/components.ts +5 -0
  27. package/src/server/plugins/engine/components/index.ts +1 -0
  28. package/src/server/plugins/engine/views/components/notificationbanner.html +15 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.15.0",
3
+ "version": "4.17.0",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -87,7 +87,7 @@
87
87
  },
88
88
  "license": "SEE LICENSE IN LICENSE",
89
89
  "dependencies": {
90
- "@defra/forms-model": "^3.0.674",
90
+ "@defra/forms-model": "^3.0.678",
91
91
  "@defra/hapi-tracing": "^1.29.0",
92
92
  "@defra/interactive-map": "^0.0.22-alpha",
93
93
  "@elastic/ecs-pino-format": "^1.5.0",
@@ -54,3 +54,22 @@
54
54
  @extend %govuk-section-break--xl;
55
55
  }
56
56
  }
57
+
58
+ .govuk-notification-banner__content {
59
+ .app-prose-scope > :last-child {
60
+ margin-bottom: 0;
61
+ }
62
+ }
63
+
64
+ // GOV.UK Frontend does not expose placeholder selectors for these — class selectors are the only option.
65
+ .app-prose-notification-banner-scope {
66
+ p {
67
+ // stylelint-disable-next-line scss/at-extend-no-missing-placeholder
68
+ @extend .govuk-notification-banner__heading;
69
+ }
70
+
71
+ a {
72
+ // stylelint-disable-next-line scss/at-extend-no-missing-placeholder
73
+ @extend .govuk-notification-banner__link;
74
+ }
75
+ }
@@ -78,6 +78,25 @@ pages:
78
78
  id: '00738799-3489-4ab2-a57b-542eecb31bfa'
79
79
  next: []
80
80
  id: da0fbdb4-a2de-4650-be16-9ba552af135f
81
+ - title: ''
82
+ path: '/notification-demo'
83
+ components:
84
+ - type: NotificationBanner
85
+ title: Important
86
+ name: notificationImportant
87
+ content: 'Contact us if you need [help with your application](https://www.defra.gov.uk).'
88
+ options:
89
+ heading: There may be a delay in processing your application.
90
+ id: ca6b6590-1608-49f4-82f9-9d92e38e66ea
91
+ - type: NotificationBanner
92
+ title: Success
93
+ name: notificationSuccess
94
+ content: 'Your application has been submitted.'
95
+ options:
96
+ type: success
97
+ id: b6960f4b-d771-4956-9c9b-17da5c603062
98
+ next: []
99
+ id: be4b4b86-514c-43bb-8cdc-f9aed6155924
81
100
  - id: 449a45f6-4541-4a46-91bd-8b8931b07b50
82
101
  title: ''
83
102
  path: '/summary'
@@ -229,7 +229,9 @@ export class DatePartsField extends FormComponent {
229
229
  ],
230
230
  advancedSettingsErrors: [
231
231
  { type: 'dateMin', template: messageTemplate.dateMin },
232
- { type: 'dateMax', template: messageTemplate.dateMax }
232
+ { type: 'dateMax', template: messageTemplate.dateMax },
233
+ { type: 'earliestDate', template: messageTemplate.dateMin },
234
+ { type: 'latestDate', template: messageTemplate.dateMax }
233
235
  ]
234
236
  }
235
237
  }
@@ -281,20 +283,38 @@ export function getValidatorDate(component: DatePartsField) {
281
283
  ? sub(startOfToday(), { days: options.maxDaysInPast })
282
284
  : undefined
283
285
 
286
+ if (dateMin && date < dateMin) {
287
+ return helpers.error('date.min', { ...context, limit: dateMin })
288
+ }
289
+
284
290
  // Maximum date from today
285
291
  const dateMax =
286
292
  options.maxDaysInFuture || options.maxDaysInFuture === 0
287
293
  ? add(startOfToday(), { days: options.maxDaysInFuture })
288
294
  : undefined
289
295
 
290
- if (dateMin && date < dateMin) {
291
- return helpers.error('date.min', { ...context, limit: dateMin })
292
- }
293
-
294
296
  if (dateMax && date > dateMax) {
295
297
  return helpers.error('date.max', { ...context, limit: dateMax })
296
298
  }
297
299
 
300
+ // Minimum date from today
301
+ const earliestDate = options.earliestDate
302
+ ? new Date(options.earliestDate)
303
+ : undefined
304
+
305
+ if (earliestDate && date < earliestDate) {
306
+ return helpers.error('date.min', { ...context, limit: earliestDate })
307
+ }
308
+
309
+ // Maximum date from today
310
+ const latestDate = options.latestDate
311
+ ? new Date(options.latestDate)
312
+ : undefined
313
+
314
+ if (latestDate && date > latestDate) {
315
+ return helpers.error('date.max', { ...context, limit: latestDate })
316
+ }
317
+
298
318
  return payload
299
319
  }
300
320
 
@@ -1,5 +1,5 @@
1
1
  import { ComponentType, type MonthYearFieldComponent } from '@defra/forms-model'
2
- import { format, isValid } from 'date-fns'
2
+ import { format, isValid, parse } from 'date-fns'
3
3
  import {
4
4
  type Context,
5
5
  type CustomValidator,
@@ -259,6 +259,34 @@ export function getValidatorMonthYear(component: MonthYearField) {
259
259
  : payload
260
260
  }
261
261
 
262
+ const date = parse(
263
+ `${values.year}-${values.month}-01`,
264
+ 'yyyy-MM-dd',
265
+ new Date()
266
+ )
267
+
268
+ if (!isValid(date)) {
269
+ return helpers.error('date.format', context)
270
+ }
271
+
272
+ // Minimum date from today
273
+ const earliestDate = options.earliestMonthYear
274
+ ? new Date(`${options.earliestMonthYear}-01`)
275
+ : undefined
276
+
277
+ if (earliestDate && date < earliestDate) {
278
+ return helpers.error('date.min', { ...context, limit: earliestDate })
279
+ }
280
+
281
+ // Maximum date from today
282
+ const latestDate = options.latestMonthYear
283
+ ? new Date(`${options.latestMonthYear}-01`)
284
+ : undefined
285
+
286
+ if (latestDate && date > latestDate) {
287
+ return helpers.error('date.max', { ...context, limit: latestDate })
288
+ }
289
+
262
290
  return payload
263
291
  }
264
292
 
@@ -0,0 +1,32 @@
1
+ import { type NotificationBannerComponent } from '@defra/forms-model'
2
+
3
+ import { ComponentBase } from './ComponentBase.js'
4
+
5
+ export class NotificationBanner extends ComponentBase {
6
+ declare options: NotificationBannerComponent['options']
7
+ content: NotificationBannerComponent['content']
8
+
9
+ constructor(
10
+ def: NotificationBannerComponent,
11
+ props: ConstructorParameters<typeof ComponentBase>[1]
12
+ ) {
13
+ super(def, props)
14
+
15
+ const { content, options } = def
16
+
17
+ this.content = content
18
+ this.options = options
19
+ }
20
+
21
+ getViewModel() {
22
+ const { content, title, viewModel } = this
23
+
24
+ return {
25
+ ...viewModel,
26
+ titleHtml: title,
27
+ content,
28
+ heading: this.options.heading,
29
+ type: this.options.type
30
+ }
31
+ }
32
+ }
@@ -46,6 +46,7 @@ export type Guidance =
46
46
  | InstanceType<typeof Components.Markdown>
47
47
  | InstanceType<typeof Components.InsetText>
48
48
  | InstanceType<typeof Components.List>
49
+ | InstanceType<typeof Components.NotificationBanner>
49
50
 
50
51
  // List component instances only
51
52
  export type ListField = InstanceType<
@@ -134,6 +135,10 @@ export function createComponent(
134
135
  component = new Components.Markdown(def, options)
135
136
  break
136
137
 
138
+ case ComponentType.NotificationBanner:
139
+ component = new Components.NotificationBanner(def, options)
140
+ break
141
+
137
142
  case ComponentType.MultilineTextField:
138
143
  component = new Components.MultilineTextField(def, options)
139
144
  break
@@ -15,6 +15,7 @@ export { Html } from './Html.js'
15
15
  export { InsetText } from './InsetText.js'
16
16
  export { List } from './List.js'
17
17
  export { Markdown } from './Markdown.js'
18
+ export { NotificationBanner } from './NotificationBanner.js'
18
19
  export { MonthYearField } from './MonthYearField.js'
19
20
  export { MultilineTextField } from './MultilineTextField.js'
20
21
  export { NumberField } from './NumberField.js'
@@ -0,0 +1,15 @@
1
+ {% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %}
2
+
3
+ {% macro NotificationBanner(component) %}
4
+ {%- set bannerHtml -%}
5
+ {%- if component.model.heading -%}
6
+ <div class="app-prose-notification-banner-scope">{{ component.model.heading | markdown | safe }}</div>
7
+ {%- endif -%}
8
+ <div class="app-prose-scope">{{ component.model.content | markdown | safe }}</div>
9
+ {%- endset -%}
10
+ {{ govukNotificationBanner({
11
+ titleHtml: component.model.titleHtml,
12
+ html: bannerHtml,
13
+ type: component.model.type
14
+ }) }}
15
+ {% endmacro %}