From a35d7956c840a379bbddd2e435975fb98a5e2910 Mon Sep 17 00:00:00 2001 From: Greg Tyler Date: Mon, 8 Jan 2024 09:47:22 +0000 Subject: [PATCH] feat(date-picker): add date picker component Can be initialised in Nunjucks with the `mojDatePicker` macro, or in JavaScript with the `data-module="moj-date-picker"` attribute. Fixes MDS-72 --- docs/examples/date-picker/index.njk | 19 + src/moj/all.js | 5 + src/moj/components/_all.scss | 1 + .../components/date-picker/_date-picker.scss | 278 ++++++++ src/moj/components/date-picker/date-picker.js | 596 ++++++++++++++++++ src/moj/components/date-picker/macro.njk | 3 + src/moj/components/date-picker/template.njk | 64 ++ 7 files changed, 966 insertions(+) create mode 100644 docs/examples/date-picker/index.njk create mode 100644 src/moj/components/date-picker/_date-picker.scss create mode 100644 src/moj/components/date-picker/date-picker.js create mode 100644 src/moj/components/date-picker/macro.njk create mode 100644 src/moj/components/date-picker/template.njk diff --git a/docs/examples/date-picker/index.njk b/docs/examples/date-picker/index.njk new file mode 100644 index 00000000..ca0d19f6 --- /dev/null +++ b/docs/examples/date-picker/index.njk @@ -0,0 +1,19 @@ +--- +layout: layouts/example.njk +title: Date Picker (example) +arguments: date-picker +--- + +{%- from "moj/components/date-picker/macro.njk" import mojDatePicker -%} + +{{ mojDatePicker({ + id: "submisison-date", + name: "submisison-date", + label: { + text: "Submission date" + }, + hint: { + text: "For example, 03/01/2024" + }, + value: "03/01/2024" +}) }} diff --git a/src/moj/all.js b/src/moj/all.js index a4090064..f7c41dda 100644 --- a/src/moj/all.js +++ b/src/moj/all.js @@ -66,4 +66,9 @@ MOJFrontend.initAll = function (options) { table: $table }); }); + + const $datepickers = document.querySelectorAll('[data-module="moj-date-picker"]') + MOJFrontend.nodeListForEach($datepickers, function ($datepicker) { + new MOJFrontend.DatePicker($datepicker, {}).init(); + }) } diff --git a/src/moj/components/_all.scss b/src/moj/components/_all.scss index ffa08e9f..19812f8e 100755 --- a/src/moj/components/_all.scss +++ b/src/moj/components/_all.scss @@ -5,6 +5,7 @@ @import "button-menu/button-menu"; @import "cookie-banner/cookie-banner"; @import "currency-input/currency-input"; +@import "date-picker/date-picker"; @import "filter/filter"; @import "header/header"; @import "identity-bar/identity-bar"; diff --git a/src/moj/components/date-picker/_date-picker.scss b/src/moj/components/date-picker/_date-picker.scss new file mode 100644 index 00000000..fd28c20d --- /dev/null +++ b/src/moj/components/date-picker/_date-picker.scss @@ -0,0 +1,278 @@ +.moj-datepicker { + position: relative; + + &--fixed-width { + .moj-datepicker-input__wrapper { + width: 215px; + } + } + + &__dialog { + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.15); + + background-color: govuk-colour('white'); + clear: both; + display: none; + padding: 8px; + outline: 1px solid $govuk-border-colour; + outline-offset: -1px; + position: static; + top: 0; + transition: background-color 0.2s, outline-color 0.2s; + width: 280px; + z-index: 2; + + &__header { + position: relative; + text-align: center; + margin-bottom: 5px; + + > :nth-child(1) { + position: absolute; + left: 5px; + top: -2px; + + > :nth-child(2) { + margin-left: 4px; + } + } + + > :nth-child(3) { + position: absolute; + right: 5px; + top: -2px; + + > :nth-child(1) { + margin-right: 4px; + } + } + } + + &__title { + font-size: 16px; + padding: 8px 0; + margin: 0 !important; + } + + &__navbuttons { + button { + background-color: transparent; + color: $govuk-text-colour !important; + min-height: 40px; + margin: 0; + padding: 4px 4px 0 4px; + min-width: 32px; + border: none; + display: inline-block; + cursor: pointer; + outline: none; + + .moj-datepicker-icon { + height: 32px; + padding: 0; + position: static; + width: 24px; + } + + &:hover { + background-color: rgba(govuk-colour('yellow'), .5); + } + + &:focus { + background-color: $govuk-focus-colour; + border-bottom: 4px solid govuk-colour('black'); + } + } + } + + &__table { + border-collapse: collapse; + + tbody:focus-within { + outline: 2px solid $govuk-focus-colour; + } + + td { + border: 0; + margin: 0; + outline: 0; + padding: 0; + } + + th { + font-size: 16px; + color: $govuk-text-colour; + } + + button { + background-color: transparent; + border-width: 0; + color: $govuk-text-colour; + min-height: 40px; + margin: 0; + padding: 0; + min-width: 40px; + + font-size: 16px; + + &:hover { + outline: 3px solid rgba(0,0,0,0); + color: $govuk-text-colour; + background-color: rgba(govuk-colour('yellow'), .5); + box-shadow: none; + text-decoration: none; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; + cursor: pointer; + } + + &:focus { + outline: 3px solid rgba(0,0,0,0); + color: $govuk-focus-text-colour; + background-color: $govuk-focus-colour; + border-bottom: 4px solid govuk-colour('black'); + padding-top: 4px; + text-decoration: none; + -webkit-box-decoration-break: clone; + box-decoration-break: clone; + } + + &[disabled="true"] { + background-color: govuk-colour('light-grey'); + color: $govuk-text-colour; + } + + &.moj-datepicker__current { + $moj-current-outline-width: 2px; + outline: $moj-current-outline-width solid govuk-colour('black') !important; + outline-offset: #{$moj-current-outline-width * -1}; + } + + &.moj-datepicker__current[tabindex="-1"] { + background: transparent; + color: currentColor; + + &:hover { + background-color: rgba(govuk-colour('yellow'), .5); + cursor: pointer; + } + } + + &.moj-datepicker__today { + font-weight: 700; + + &::after { + background-color: currentColor; + border-radius: 4px; + content: ''; + height: 4px; + margin-top: -1px; + margin-left: 1px; + position: absolute; + width: 4px; + } + } + + &.moj-datepicker-selected:not(:focus) { + background-color: govuk-colour('black'); + color: govuk-colour('white'); + } + } + } + + &__table-caption { + font-size: 14px; + caption-side: bottom; + line-height: 2; + margin-top: 8px; + } + + &__buttongroup { + display: grid; + grid-gap: 0 8px; + margin-right: 0; + overflow: visible; + grid-template-columns: 1fr 1fr; + + > * { + margin-left: 0; + margin-right: 0; + width: auto !important; + } + + .govuk-button { + margin-bottom: 0; + } + } + } + + .govuk-label--m { + margin-bottom: 5px; + } + + .govuk-hint { + margin-bottom: 10px; + } +} + +.moj-datepicker-input__wrapper { + display: flex; + position: relative; + margin-bottom: 24px; + overflow: visible; + + .govukInput { + float: left; + margin-bottom: 0; + margin-right: -48px; + padding-right: 56px; + } + + .moj-datepicker-icon { + height: 24px; + width: 32px; + } + + .govuk-form-group { + width: 100%; + } +} + +@media (min-width: 768px) { + .moj-datepicker { + &__dialog { + position: absolute; + width: auto; + } + } +} + +.moj-datepicker-button { + background-color: govuk-colour('black'); + fill: govuk-colour('white'); + position: absolute; + right: 0; + bottom: 0; + height: 40px; + padding-top: 6px; + border: none; + border-bottom: 4px solid govuk-colour('black'); + outline: none; + cursor: pointer; + + &:hover { + background-color: govuk-colour('mid-grey'); + fill: govuk-colour('black'); + border-bottom: 4px solid govuk-colour('mid-grey'); + } + + &:focus { + background-color: $govuk-focus-colour; + fill: $govuk-focus-text-colour; + border-bottom: 4px solid govuk-colour('black'); + } + + @media (max-width: 768px) { + bottom: unset; + } +} diff --git a/src/moj/components/date-picker/date-picker.js b/src/moj/components/date-picker/date-picker.js new file mode 100644 index 00000000..2d418815 --- /dev/null +++ b/src/moj/components/date-picker/date-picker.js @@ -0,0 +1,596 @@ +/** + * Datepicker config + * + * @typedef {object} DatepickerConfig + * + * @property {string} [imagePath] - The path to image assets. + * @property {string} [id] - . + * @property {string} [name] - . + * @property {string} [label] - . + * @property {string} [hint] - . + * @property {string} [minDate] - . + * @property {string} [maxDate] - . + */ + +/** + * Datepicker component + * + * @param {HTMLElement} $module - HTML element + * @param {DatepickerConfig} config - Datepicker config + * @constructor + */ +function Datepicker($module, config) { + if (!$module) { + return this + } + const defaultConfig = { + imagePath: '/assets/images/', + } + this.config = { ...defaultConfig, ...config } + + this.dayLabels = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] + this.monthLabels = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ] + + this.currentDate = new Date() + this.currentDate.setHours(0, 0, 0, 0) + this.calendarDays = [] + + this.keycodes = { + tab: 9, + esc: 27, + pageup: 33, + pagedown: 34, + end: 35, + home: 36, + left: 37, + up: 38, + right: 39, + down: 40, + } + + this.$module = $module + this.$input = $module.querySelector('.moj-js-datepicker-input') + this.$calendarButton = $module.querySelector('.moj-js-datepicker-button') +} + +/** + * Initialise Datepicker + */ +Datepicker.prototype.init = function () { + // Check that required elements are present + if (!this.$input) { + return + } + + this.initControls() +} + +/** + * Initialise controls and set attributes + */ +Datepicker.prototype.initControls = function () { + // Create datepicker popup dialog + const titleId = `datepicker-title-${this.$input.id}` + const dialog = document.createElement('div') + dialog.id = `datepicker-${this.$input.id}` + dialog.setAttribute('class', 'moj-datepicker__dialog datepickerDialog') + dialog.setAttribute('role', 'dialog') + dialog.setAttribute('aria-modal', 'true') + dialog.setAttribute('aria-labelledby', titleId) + dialog.innerHTML = this.createDialogMarkup(titleId) + + this.dialogElement = dialog + this.$input.insertAdjacentElement('afterend', this.dialogElement) + + this.dialogTitleNode = this.dialogElement.querySelector('.js-datepicker-month-year') + + this.setMinAndMaxDatesOnCalendar() + + // create calendar + const tbody = this.dialogElement.querySelector('tbody') + let dayCount = 0 + for (let i = 0; i < 6; i++) { + // create row + const row = tbody.insertRow(i) + + for (let j = 0; j < 7; j++) { + // create cell (day) + const cell = document.createElement('td') + const dateButton = document.createElement('button') + dateButton.dataset.form = 'date-select' + + cell.appendChild(dateButton) + row.appendChild(cell) + + const calendarDay = new DSCalendarDay(dateButton, dayCount, i, j, this) + calendarDay.init() + this.calendarDays.push(calendarDay) + dayCount++ + } + } + + // add event listeners + this.prevMonthButton = this.dialogElement.querySelector('.js-datepicker-prev-month') + this.prevYearButton = this.dialogElement.querySelector('.js-datepicker-prev-year') + this.nextMonthButton = this.dialogElement.querySelector('.js-datepicker-next-month') + this.nextYearButton = this.dialogElement.querySelector('.js-datepicker-next-year') + this.prevMonthButton.addEventListener('click', event => this.focusPreviousMonth(event, false)) + this.prevYearButton.addEventListener('click', event => this.focusPreviousYear(event, false)) + this.nextMonthButton.addEventListener('click', event => this.focusNextMonth(event, false)) + this.nextYearButton.addEventListener('click', event => this.focusNextYear(event, false)) + + this.cancelButton = this.dialogElement.querySelector('.js-datepicker-cancel') + this.okButton = this.dialogElement.querySelector('.js-datepicker-ok') + this.cancelButton.addEventListener('click', event => { + event.preventDefault() + this.closeDialog(event) + }) + this.okButton.addEventListener('click', () => this.selectDate(this.currentDate)) + + const dialogButtons = this.dialogElement.querySelectorAll('button:not([disabled="true"])') + // eslint-disable-next-line prefer-destructuring + this.firstButtonInDialog = dialogButtons[0] + this.lastButtonInDialog = dialogButtons[dialogButtons.length - 1] + this.firstButtonInDialog.addEventListener('keydown', event => this.firstButtonKeyup(event)) + this.lastButtonInDialog.addEventListener('keydown', event => this.lastButtonKeyup(event)) + + this.$calendarButton.addEventListener('click', event => this.toggleDialog(event)) + + document.body.addEventListener('mouseup', event => this.backgroundClick(event)) + + // populates calendar with initial dates, avoids Wave errors about null buttons + this.updateCalendar() +} + +Datepicker.prototype.createDialogMarkup = function (titleId) { + return `
+
+ + + +
+ +

June 2020

+ +
+ + + +
+
+ + + + + + + + + + + + + + + + +
You can use the arrow keys to select a date
MoTuWeThFrSaSu
+ +
+ + +
` +} + +Datepicker.prototype.leadingZeroes = function (value, length = 2) { + let ret = value.toString() + + while (ret.length < length) { + ret = `0${ret.toString()}` + } + + return ret +} + +Datepicker.prototype.setMinAndMaxDatesOnCalendar = function () { + if (this.$input.dataset.mindate) { + this.minDate = this.formattedDateFromString(this.$input.dataset.mindate, null) + if (this.minDate && this.currentDate < this.minDate) { + this.currentDate = this.minDate + } + } + + if (this.$input.dataset.maxdate) { + this.maxDate = this.formattedDateFromString(this.$input.dataset.maxdate, null) + if (this.maxDate && this.currentDate > this.maxDate) { + this.currentDate = this.maxDate + } + } +} + +Datepicker.prototype.formattedDateFromString = function (dateString, fallback = new Date()) { + let formattedDate = null + const dateFormatPattern = /(\d{1,2})([-/,. ])(\d{1,2})[-/,. ](\d{4})/ + + if (!dateFormatPattern.test(dateString)) return fallback + + const match = dateString.match(dateFormatPattern) + const separator = match[2] + const day = match[1] + const month = match[3] + const year = match[4] + + formattedDate = new Date(`${month}${separator}${day}${separator}${year}`) + if (formattedDate instanceof Date && !isNaN(formattedDate)) { + return formattedDate + } + return fallback +} + +Datepicker.prototype.formattedDateFromDate = function (date) { + return `${this.leadingZeroes(date.getDate())}/${this.leadingZeroes(date.getMonth() + 1)}/${date.getFullYear()}` +} + +Datepicker.prototype.backgroundClick = function (event) { + if ( + this.isOpen() && + !this.dialogElement.contains(event.target) && + !this.$input.contains(event.target) && + !this.$calendarButton.contains(event.target) + ) { + event.preventDefault() + this.closeDialog() + } +} + +Datepicker.prototype.formattedDateHuman = function (date) { + return `${this.dayLabels[date.getDay()]} ${date.getDate()} ${this.monthLabels[date.getMonth()]} ${date.getFullYear()}` +} + +Datepicker.prototype.firstButtonKeyup = function (event) { + if (event.keyCode === this.keycodes.tab && event.shiftKey) { + this.lastButtonInDialog.focus() + event.preventDefault() + } +} + +Datepicker.prototype.lastButtonKeyup = function (event) { + if (event.keyCode === this.keycodes.tab && !event.shiftKey) { + this.firstButtonInDialog.focus() + event.preventDefault() + } +} + +// render calendar +Datepicker.prototype.updateCalendar = function () { + this.dialogTitleNode.innerHTML = `${this.monthLabels[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}` + + const day = this.currentDate + + const firstOfMonth = new Date(day.getFullYear(), day.getMonth(), 1) + const dayOfWeek = firstOfMonth.getDay() === 0 ? 6 : firstOfMonth.getDay() - 1 // Change logic to make Monday first day of week, i.e. 0 + + firstOfMonth.setDate(firstOfMonth.getDate() - dayOfWeek) + + const thisDay = new Date(firstOfMonth) + + // loop through our days + for (let i = 0; i < this.calendarDays.length; i++) { + const hidden = thisDay.getMonth() !== day.getMonth() + + let disabled + + if (thisDay < this.minDate) { + disabled = true + } + if (thisDay > this.maxDate) { + disabled = true + } + + this.calendarDays[i].update(thisDay, hidden, disabled) + + thisDay.setDate(thisDay.getDate() + 1) + } +} + +Datepicker.prototype.setCurrentDate = function (focus = true) { + const { currentDate } = this + + this.calendarDays.forEach(calendarDay => { + calendarDay.button.setAttribute('tabindex', -1) + calendarDay.button.classList.remove('moj-datepicker-selected') + const calendarDayDate = calendarDay.date + calendarDayDate.setHours(0, 0, 0, 0) + + const today = new Date() + today.setHours(0, 0, 0, 0) + + if (calendarDayDate.getTime() === currentDate.getTime() && !calendarDay.disabled) { + if (focus) { + calendarDay.button.setAttribute('tabindex', 0) + calendarDay.button.focus() + calendarDay.button.classList.add('moj-datepicker-selected') + } + } + + if (this.inputDate && calendarDayDate.getTime() === this.inputDate.getTime()) { + calendarDay.button.classList.add('moj-datepicker__current') + calendarDay.button.setAttribute('aria-selected', true) + } else { + calendarDay.button.classList.remove('moj-datepicker__current') + calendarDay.button.removeAttribute('aria-selected') + } + + if (calendarDayDate.getTime() === today.getTime()) { + calendarDay.button.classList.add('moj-datepicker__today') + } else { + calendarDay.button.classList.remove('moj-datepicker__today') + } + }) + + // if no date is tab-able, make the first non-disabled date tab-able + if (!focus) { + const enabledDays = this.calendarDays.filter(calendarDay => { + return window.getComputedStyle(calendarDay.button).display === 'block' && !calendarDay.button.disabled + }) + + enabledDays[0].button.setAttribute('tabindex', 0) + + this.currentDate = enabledDays[0].date + } +} + +Datepicker.prototype.selectDate = function (date) { + this.$calendarButton.querySelector('span').innerText = `Choose date. Selected date is ${this.formattedDateHuman( + date, + )}` + this.$input.value = this.formattedDateFromDate(date) + + const changeEvent = new Event('change', { bubbles: true, cancelable: true }) + this.$input.dispatchEvent(changeEvent) + + this.closeDialog() +} + +Datepicker.prototype.isOpen = function () { + return this.dialogElement.classList.contains('moj-datepicker__dialog--open') +} + +Datepicker.prototype.toggleDialog = function (event) { + event.preventDefault() + if (this.isOpen()) { + this.closeDialog() + } else { + this.setMinAndMaxDatesOnCalendar() + this.openDialog() + } +} + +Datepicker.prototype.openDialog = function () { + // display the dialog + this.dialogElement.style.display = 'block' + this.dialogElement.classList.add('moj-datepicker__dialog--open') + + // position the dialog + this.dialogElement.style.left = `${this.$input.offsetWidth + 16}px` + + // get the date from the input element + if (this.$input.value.match(/^(\d{1,2})([-/,. ])(\d{1,2})[-/,. ](\d{4})$/)) { + this.inputDate = this.formattedDateFromString(this.$input.value) + this.currentDate = this.inputDate + } + + this.updateCalendar() + this.setCurrentDate() +} + +Datepicker.prototype.closeDialog = function () { + this.dialogElement.style.display = 'none' + this.dialogElement.classList.remove('moj-datepicker__dialog--open') + this.$calendarButton.focus() +} + +Datepicker.prototype.goToDate = function (date, focus) { + const current = this.currentDate + this.currentDate = date + + if (this.minDate && this.minDate > date) { + this.currentDate = this.minDate + } else if (this.maxDate && this.maxDate < date) { + this.currentDate = this.maxDate + } + + if (current.getMonth() !== this.currentDate.getMonth() || current.getFullYear() !== this.currentDate.getFullYear()) { + this.updateCalendar() + } + + this.setCurrentDate(focus) +} + +// day navigation +Datepicker.prototype.focusNextDay = function () { + const date = new Date(this.currentDate) + date.setDate(date.getDate() + 1) + this.goToDate(date) +} + +Datepicker.prototype.focusPreviousDay = function () { + const date = new Date(this.currentDate) + date.setDate(date.getDate() - 1) + this.goToDate(date) +} + +// week navigation +Datepicker.prototype.focusNextWeek = function () { + const date = new Date(this.currentDate) + date.setDate(date.getDate() + 7) + this.goToDate(date) +} + +Datepicker.prototype.focusPreviousWeek = function () { + const date = new Date(this.currentDate) + date.setDate(date.getDate() - 7) + this.goToDate(date) +} + +Datepicker.prototype.focusFirstDayOfWeek = function () { + const date = new Date(this.currentDate) + date.setDate(date.getDate() - date.getDay()) + this.goToDate(date) +} + +Datepicker.prototype.focusLastDayOfWeek = function () { + const date = new Date(this.currentDate) + date.setDate(date.getDate() - date.getDay() + 6) + this.goToDate(date) +} + +// month navigation +Datepicker.prototype.focusNextMonth = function (event, focus = true) { + event.preventDefault() + const date = new Date(this.currentDate) + date.setMonth(date.getMonth() + 1, 1) + this.goToDate(date, focus) +} + +Datepicker.prototype.focusPreviousMonth = function (event, focus = true) { + event.preventDefault() + const date = new Date(this.currentDate) + date.setMonth(date.getMonth() - 1, 1) + this.goToDate(date, focus) +} + +// year navigation +Datepicker.prototype.focusNextYear = function (event, focus = true) { + event.preventDefault() + const date = new Date(this.currentDate) + date.setFullYear(date.getFullYear() + 1, date.getMonth(), 1) + this.goToDate(date, focus) +} + +Datepicker.prototype.focusPreviousYear = function (event, focus = true) { + event.preventDefault() + const date = new Date(this.currentDate) + date.setFullYear(date.getFullYear() - 1, date.getMonth(), 1) + this.goToDate(date, focus) +} + +/** + * + * @param button + * @param index + * @param row + * @param column + * @param picker + * @constructor + */ +function DSCalendarDay(button, index, row, column, picker) { + this.index = index + this.row = row + this.column = column + this.button = button + this.picker = picker + + this.date = new Date() +} + +DSCalendarDay.prototype.init = function () { + this.button.addEventListener('keydown', this.keyPress.bind(this)) + this.button.addEventListener('click', this.click.bind(this)) +} + +DSCalendarDay.prototype.update = function (day, hidden, disabled) { + this.button.innerHTML = day.getDate() + this.date = new Date(day) + + if (disabled) { + this.button.setAttribute('disabled', true) + } else { + this.button.removeAttribute('disabled') + } + + if (hidden) { + this.button.style.display = 'none' + } else { + this.button.style.display = 'block' + } +} + +DSCalendarDay.prototype.click = function (event) { + this.picker.goToDate(this.date) + this.picker.selectDate(this.date) + + event.stopPropagation() + event.preventDefault() +} + +DSCalendarDay.prototype.keyPress = function (event) { + let calendarNavKey = true + + switch (event.keyCode) { + case this.picker.keycodes.left: + this.picker.focusPreviousDay() + break + case this.picker.keycodes.right: + this.picker.focusNextDay() + break + case this.picker.keycodes.up: + this.picker.focusPreviousWeek() + break + case this.picker.keycodes.down: + this.picker.focusNextWeek() + break + case this.picker.keycodes.home: + this.picker.focusFirstDayOfWeek() + break + case this.picker.keycodes.end: + this.picker.focusLastDayOfWeek() + break + case this.picker.keycodes.pageup: + // eslint-disable-next-line no-unused-expressions + event.shiftKey ? this.picker.focusPreviousYear(event) : this.picker.focusPreviousMonth(event) + break + case this.picker.keycodes.pagedown: + // eslint-disable-next-line no-unused-expressions + event.shiftKey ? this.picker.focusNextYear(event) : this.picker.focusNextMonth(event) + break + case this.picker.keycodes.esc: + this.picker.closeDialog() + break + default: + calendarNavKey = false + break + } + + if (calendarNavKey) { + event.preventDefault() + event.stopPropagation() + } +} + +MOJFrontend.DatePicker = Datepicker; diff --git a/src/moj/components/date-picker/macro.njk b/src/moj/components/date-picker/macro.njk new file mode 100644 index 00000000..edff875a --- /dev/null +++ b/src/moj/components/date-picker/macro.njk @@ -0,0 +1,3 @@ +{% macro mojDatePicker(params) %} + {%- include "./template.njk" -%} +{% endmacro %} diff --git a/src/moj/components/date-picker/template.njk b/src/moj/components/date-picker/template.njk new file mode 100644 index 00000000..a384258d --- /dev/null +++ b/src/moj/components/date-picker/template.njk @@ -0,0 +1,64 @@ +{% from "govuk/components/input/macro.njk" import govukInput %} +{% from "govuk/components/label/macro.njk" import govukLabel %} +{% from "govuk/components/hint/macro.njk" import govukHint %} +{% from "govuk/components/error-message/macro.njk" import govukErrorMessage %} + +
+ {{ govukLabel({ + html: params.label.html, + text: params.label.text, + classes: params.label.classes, + isPageHeading: params.label.isPageHeading, + attributes: params.label.attributes, + for: params.id + }) | indent(2) | trim }} + {% if params.hint %} + {% set hintId = params.id + "-hint" %} + {% set describedBy = describedBy + " " + hintId if describedBy else hintId %} + {{ govukHint({ + id: hintId, + classes: params.hint.classes, + attributes: params.hint.attributes, + html: params.hint.html, + text: params.hint.text + }) | indent(2) | trim }} + {% endif %} + {% if params.errorMessage %} + {% set errorId = params.id + "-error" %} + {% set describedBy = describedBy + " " + errorId if describedBy else errorId %} + {{ govukErrorMessage({ + id: errorId, + classes: params.errorMessage.classes, + attributes: params.errorMessage.attributes, + html: params.errorMessage.html, + text: params.errorMessage.text, + visuallyHiddenText: params.errorMessage.visuallyHiddenText + }) | indent(2) | trim }} + {% endif %} +
+ {{ govukInput({ + classes: "govuk-input moj-js-datepicker-input", + id: params.id, + name: params.name, + value: params.value, + autocomplete: "off", + attributes: { + "maxlength": "10", + "data-mindate": params.minDate, + "data-maxdate": params.maxDate + } + }) }} + +
+