From a0fa4074050f9400a487d6294d8f8cbd787d0da3 Mon Sep 17 00:00:00 2001 From: Andy Jones Date: Mon, 29 Apr 2024 15:29:12 +0100 Subject: [PATCH 1/8] Card branch --- .DS_Store | Bin 8196 -> 8196 bytes app/.DS_Store | Bin 8196 -> 8196 bytes app/assets/.DS_Store | Bin 10244 -> 10244 bytes app/assets/js/dfefrontend.js | 2 +- app/assets/js/govuk/all.bundle.js | 454 +- app/assets/scss/app.scss | 48 +- app/controllers/designSystemController.js | 2 +- app/views/.DS_Store | Bin 10244 -> 10244 bytes .../components/card/examples/everything.html | 100 + .../design-system/components/card/index.html | 105 + .../components/card/properties.html | 189 + .../components/card/variants.html | 919 + .../header/examples/everything.html | 132 +- .../components/header/index.html | 5 +- .../components/header/properties.html | 259 +- .../components/header/variants.html | 120 +- app/views/design-system/components/index.html | 17 +- .../design-system/dfe-frontend/index.html | 5 +- .../design-system/getting-started/index.html | 44 +- .../styles/typography/index.html | 39 +- app/views/includes/_side-nav.html | 9 +- app/views/layouts/_footer.html | 2 +- gulpfile.js | 14 +- index.js | 52 +- package-lock.json | 29342 +++++----------- package.json | 2 +- public/.DS_Store | Bin 8196 -> 8196 bytes public/assets/.DS_Store | Bin 10244 -> 10244 bytes public/assets/css/app.min.css | 4 +- public/assets/js/dfefrontend.min.js | 2 +- public/assets/js/govuk/all.bundle.min.js | 2 +- 31 files changed, 11002 insertions(+), 20867 deletions(-) create mode 100644 app/views/design-system/components/card/examples/everything.html create mode 100644 app/views/design-system/components/card/index.html create mode 100644 app/views/design-system/components/card/properties.html create mode 100644 app/views/design-system/components/card/variants.html diff --git a/.DS_Store b/.DS_Store index 934de192d8015ef507abe87ac9eeb63381d6457e..e53ba493c5bd54fa3acfcaed286479b11fa82cd0 100644 GIT binary patch delta 145 zcmZp1XmQw}Bgm9nH(5_m%s#?yiNE35qmB#=49pCP3yJBY|=pMQB7W)>UJq*8_?h8%`WhGb+Dp=u_#i^wxR K*t}dMmKy-`nJfta delta 145 zcmZp1XmQw}BgoX;H(5_m%zpdX?|Rz~9(813U|?oQWGG-L$jwi2Ny^Dj0*Y{8$`)i8 z1}Ep|764@#7}Xm#Hw*4&Vlwxe>>wheZPnbi^VjbqAhXzjCY3TIG2}30G9)9L2vsw= OT|}Po!sg{7vD^S?nJ;Pp diff --git a/app/.DS_Store b/app/.DS_Store index 695afbd54b1c13ebb56b6852218b2e61e9e40ab3..a8b7f8baaea9c1548b2897f2972f599853fba3b6 100644 GIT binary patch delta 271 zcmZp1XmQx!BB&DmiGhKEjUkbt7zk4tN*IcB^Icq$a`Kaa;v7>$`}_^hPR9f2Gfp%l1QxbPoADgJ^C!{Frn+ybDPKwvQW zp^&q~?uS4o3quOf?h=MXWLvEk%mtf>DvvN4BELCBIF*S>Y{}%)qEc*g4=p~k>g?p# XqB1Zhub3Po!)6_^x$K11++YL%Juq5# delta 271 zcmZp1XmQx!BB&DK!N9=4#*oNR41}o+B@D&6`7SO=Ir&LIagIsX-so*RI5|U5jt$D} z5tL`*W|+KIa4i$t%gHf9Y8*X=-+^k5Is!Q|LMd>qaN$3KQv3xOhQZ1CxdlKofk0vM zLm_8}Jr99Q7KRj{-6af($hN+Wg_wvck1!e{zd1!Xm5Ir8;pEezQfwdBs_O$C`dU;5 U#^e=~V^r9zBQ}?vu$mi;02EJKasU7T diff --git a/app/assets/.DS_Store b/app/assets/.DS_Store index 71eb2b49ac857a87862a9e9419b37425fb9dda1a..3726ed6e97677a20f9b575c205e9594661377e31 100644 GIT binary patch delta 163 zcmZn(XbIR5FRisJV7kBI*`tmO3=C`xnGCrMi45rssSL%r`6(_*Ir&LIaSn)@$(_=j zGQtyq0!$29AO$XP1#iV>0u7n0CnF{gVS>#AD=Ww_3{K9^EdXj`U|0}}),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(e){var t=this;do{if(Element.prototype.matches.call(t,e))return t;t=t.parentElement||t.parentNode}while(null!==t&&1===t.nodeType);return null})},function(e,t,r){"use strict";r.r(t);var n=function(e,t){if(e&&t){var r="true"===e.getAttribute(t)?"false":"true";e.setAttribute(t,r)}},o=function(){var e,t,r,o;e=document.querySelector("#toggle-menu"),t=document.querySelector("#close-menu"),r=document.querySelector("#header-navigation"),o=function(t){t.preventDefault(),n(e,"aria-expanded"),e.classList.toggle("is-active"),r.classList.toggle("js-show")},e&&t&&r&&[e,t].forEach((function(e){e.addEventListener("click",o)})),function(){var e=document.querySelector("#toggle-search"),t=document.querySelector("#close-search"),r=document.querySelector("#wrap-search"),o=document.querySelector("#content-header"),c=function(t){t.preventDefault(),n(e,"aria-expanded"),e.classList.toggle("is-active"),r.classList.toggle("js-show"),o.classList.toggle("js-show")};e&&t&&[e,t].forEach((function(e){e.addEventListener("click",c)}))}()};r(0);document.addEventListener("DOMContentLoaded",(function(){o()}))}]); \ No newline at end of file +(()=>{var e={621:()=>{NodeList.prototype.forEach||(NodeList.prototype.forEach=Array.prototype.forEach),Array.prototype.includes||Object.defineProperty(Array.prototype,"includes",{enumerable:!1,value:function(e){return this.filter((function(t){return t===e})).length>0}}),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(e){var t=this;do{if(Element.prototype.matches.call(t,e))return t;t=t.parentElement||t.parentNode}while(null!==t&&1===t.nodeType);return null})}},t={};function r(o){var n=t[o];if(void 0!==n)return n.exports;var c=t[o]={exports:{}};return e[o](c,c.exports,r),c.exports}(()=>{"use strict";var e=function(e,t){if(e&&t){var r="true"===e.getAttribute(t)?"false":"true";e.setAttribute(t,r)}};r(621),document.addEventListener("DOMContentLoaded",(function(){var t,r,o,n;t=document.querySelector("#toggle-menu"),r=document.querySelector("#close-menu"),o=document.querySelector("#header-navigation"),n=function(r){r.preventDefault(),e(t,"aria-expanded"),t.classList.toggle("is-active"),o.classList.toggle("js-show")},t&&r&&o&&[t,r].forEach((function(e){e.addEventListener("click",n)})),function(){var t=document.querySelector("#toggle-search"),r=document.querySelector("#close-search"),o=document.querySelector("#wrap-search"),n=document.querySelector("#content-header"),c=function(r){r.preventDefault(),e(t,"aria-expanded"),t.classList.toggle("is-active"),o.classList.toggle("js-show"),n.classList.toggle("js-show")};t&&r&&[t,r].forEach((function(e){e.addEventListener("click",c)}))}()}))})()})(); \ No newline at end of file diff --git a/app/assets/js/govuk/all.bundle.js b/app/assets/js/govuk/all.bundle.js index cbfbcc59..e744ed7e 100644 --- a/app/assets/js/govuk/all.bundle.js +++ b/app/assets/js/govuk/all.bundle.js @@ -4,46 +4,77 @@ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {})); })(this, (function (exports) { 'use strict'; - const version = '5.1.0'; + const version = '5.3.1'; + + function normaliseString(value, property) { + const trimmedValue = value ? value.trim() : ''; + let output; + let outputType = property == null ? void 0 : property.type; + if (!outputType) { + if (['true', 'false'].includes(trimmedValue)) { + outputType = 'boolean'; + } + if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) { + outputType = 'number'; + } + } + switch (outputType) { + case 'boolean': + output = trimmedValue === 'true'; + break; + case 'number': + output = Number(trimmedValue); + break; + default: + output = value; + } + return output; + } + + /** + * @typedef {import('./index.mjs').SchemaProperty} SchemaProperty + */ function mergeConfigs(...configObjects) { - function flattenObject(configObject) { - const flattenedObject = {}; - function flattenLoop(obj, prefix) { - for (const [key, value] of Object.entries(obj)) { - const prefixedKey = prefix ? `${prefix}.${key}` : key; - if (value && typeof value === 'object') { - flattenLoop(value, prefixedKey); - } else { - flattenedObject[prefixedKey] = value; - } - } - } - flattenLoop(configObject); - return flattenedObject; - } const formattedConfigObject = {}; for (const configObject of configObjects) { - const obj = flattenObject(configObject); - for (const [key, value] of Object.entries(obj)) { - formattedConfigObject[key] = value; + for (const key of Object.keys(configObject)) { + const option = formattedConfigObject[key]; + const override = configObject[key]; + if (isObject(option) && isObject(override)) { + formattedConfigObject[key] = mergeConfigs(option, override); + } else { + formattedConfigObject[key] = override; + } } } return formattedConfigObject; } - function extractConfigByNamespace(configObject, namespace) { - const newObject = {}; - for (const [key, value] of Object.entries(configObject)) { + function extractConfigByNamespace(Component, dataset, namespace) { + const property = Component.schema.properties[namespace]; + if ((property == null ? void 0 : property.type) !== 'object') { + return; + } + const newObject = { + [namespace]: ({}) + }; + for (const [key, value] of Object.entries(dataset)) { + let current = newObject; const keyParts = key.split('.'); - if (keyParts[0] === namespace) { - if (keyParts.length > 1) { - keyParts.shift(); + for (const [index, name] of keyParts.entries()) { + if (typeof current === 'object') { + if (index < keyParts.length - 1) { + if (!isObject(current[name])) { + current[name] = {}; + } + current = current[name]; + } else if (key !== namespace) { + current[name] = normaliseString(value); + } } - const newKey = keyParts.join('.'); - newObject[newKey] = value; } } - return newObject; + return newObject[namespace]; } function getFragmentFromUrl(url) { if (!url.includes('#')) { @@ -93,28 +124,44 @@ const validationErrors = []; for (const [name, conditions] of Object.entries(schema)) { const errors = []; - for (const { - required, - errorMessage - } of conditions) { - if (!required.every(key => !!config[key])) { - errors.push(errorMessage); + if (Array.isArray(conditions)) { + for (const { + required, + errorMessage + } of conditions) { + if (!required.every(key => !!config[key])) { + errors.push(errorMessage); + } + } + if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) { + validationErrors.push(...errors); } - } - if (name === 'anyOf' && !(conditions.length - errors.length >= 1)) { - validationErrors.push(...errors); } } return validationErrors; } + function isArray(option) { + return Array.isArray(option); + } + function isObject(option) { + return !!option && typeof option === 'object' && !isArray(option); + } /** * Schema for component config * * @typedef {object} Schema + * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties * @property {SchemaCondition[]} [anyOf] - List of schema conditions */ + /** + * Schema property for component config + * + * @typedef {object} SchemaProperty + * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type + */ + /** * Schema condition for component config * @@ -123,26 +170,15 @@ * @property {string} errorMessage - Error message when required config fields not provided */ - function normaliseString(value) { - if (typeof value !== 'string') { - return value; - } - const trimmedValue = value.trim(); - if (trimmedValue === 'true') { - return true; - } - if (trimmedValue === 'false') { - return false; - } - if (trimmedValue.length > 0 && isFinite(Number(trimmedValue))) { - return Number(trimmedValue); - } - return value; - } - function normaliseDataset(dataset) { + function normaliseDataset(Component, dataset) { const out = {}; - for (const [key, value] of Object.entries(dataset)) { - out[key] = normaliseString(value); + for (const [field, property] of Object.entries(Component.schema.properties)) { + if (field in dataset) { + out[field] = normaliseString(dataset[field], property); + } + if ((property == null ? void 0 : property.type) === 'object') { + out[field] = extractConfigByNamespace(Component, dataset, field); + } } return out; } @@ -212,18 +248,21 @@ if (!lookupKey) { throw new Error('i18n: lookup key missing'); } - if (typeof (options == null ? void 0 : options.count) === 'number') { - lookupKey = `${lookupKey}.${this.getPluralSuffix(lookupKey, options.count)}`; + let translation = this.translations[lookupKey]; + if (typeof (options == null ? void 0 : options.count) === 'number' && typeof translation === 'object') { + const translationPluralForm = translation[this.getPluralSuffix(lookupKey, options.count)]; + if (translationPluralForm) { + translation = translationPluralForm; + } } - const translationString = this.translations[lookupKey]; - if (typeof translationString === 'string') { - if (translationString.match(/%{(.\S+)}/)) { + if (typeof translation === 'string') { + if (translation.match(/%{(.\S+)}/)) { if (!options) { throw new Error('i18n: cannot replace placeholders in string if no option data provided'); } - return this.replacePlaceholders(translationString, options); + return this.replacePlaceholders(translation, options); } - return translationString; + return translation; } return lookupKey; } @@ -251,12 +290,15 @@ if (!isFinite(count)) { return 'other'; } + const translation = this.translations[lookupKey]; const preferredForm = this.hasIntlPluralRulesSupport() ? new Intl.PluralRules(this.locale).select(count) : this.selectPluralFormUsingFallbackRules(count); - if (`${lookupKey}.${preferredForm}` in this.translations) { - return preferredForm; - } else if (`${lookupKey}.other` in this.translations) { - console.warn(`i18n: Missing plural form ".${preferredForm}" for "${this.locale}" locale. Falling back to ".other".`); - return 'other'; + if (typeof translation === 'object') { + if (preferredForm in translation) { + return preferredForm; + } else if ('other' in translation) { + console.warn(`i18n: Missing plural form ".${preferredForm}" for "${this.locale}" locale. Falling back to ".other".`); + return 'other'; + } } throw new Error(`i18n: Plural form ".other" is required for "${this.locale}" locale`); } @@ -443,8 +485,8 @@ }); } this.$module = $module; - this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset($module.dataset)); - this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n')); + this.config = mergeConfigs(Accordion.defaults, config, normaliseDataset(Accordion, $module.dataset)); + this.i18n = new I18n(this.config.i18n); const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`); if (!$sections.length) { throw new ElementError({ @@ -680,6 +722,16 @@ }, rememberExpanded: true }); + Accordion.schema = Object.freeze({ + properties: { + i18n: { + type: 'object' + }, + rememberExpanded: { + type: 'boolean' + } + } + }); const helper = { /** * Check for `window.sessionStorage`, and that it actually works. @@ -733,7 +785,10 @@ * 'Show' button's accessible name when a section is expanded. */ - const KEY_SPACE = 32; + /** + * @typedef {import('../../common/index.mjs').Schema} Schema + */ + const DEBOUNCE_TIMEOUT_IN_SECONDS = 1; /** @@ -759,13 +814,13 @@ }); } this.$module = $module; - this.config = mergeConfigs(Button.defaults, config, normaliseDataset($module.dataset)); + this.config = mergeConfigs(Button.defaults, config, normaliseDataset(Button, $module.dataset)); this.$module.addEventListener('keydown', event => this.handleKeyDown(event)); this.$module.addEventListener('click', event => this.debounce(event)); } handleKeyDown(event) { const $target = event.target; - if (event.keyCode !== KEY_SPACE) { + if (event.key !== ' ') { return; } if ($target instanceof HTMLElement && $target.getAttribute('role') === 'button') { @@ -794,10 +849,21 @@ * @property {boolean} [preventDoubleClick=false] - Prevent accidental double * clicks on submit buttons from submitting forms multiple times. */ + + /** + * @typedef {import('../../common/index.mjs').Schema} Schema + */ Button.moduleName = 'govuk-button'; Button.defaults = Object.freeze({ preventDoubleClick: false }); + Button.schema = Object.freeze({ + properties: { + preventDoubleClick: { + type: 'boolean' + } + } + }); function closestAttributeValue($element, attributeName) { const $closestElementWithAttribute = $element.closest(`[${attributeName}]`); @@ -850,7 +916,7 @@ identifier: 'Form field (`.govuk-js-character-count`)' }); } - const datasetConfig = normaliseDataset($module.dataset); + const datasetConfig = normaliseDataset(CharacterCount, $module.dataset); let configOverrides = {}; if ('maxwords' in datasetConfig || 'maxlength' in datasetConfig) { configOverrides = { @@ -863,7 +929,7 @@ if (errors[0]) { throw new ConfigError(`Character count: ${errors[0]}`); } - this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n'), { + this.i18n = new I18n(this.config.i18n, { locale: closestAttributeValue($module, 'lang') }); this.maxLength = (_ref = (_this$config$maxwords = this.config.maxwords) != null ? _this$config$maxwords : this.config.maxlength) != null ? _ref : Infinity; @@ -1076,6 +1142,20 @@ } }); CharacterCount.schema = Object.freeze({ + properties: { + i18n: { + type: 'object' + }, + maxwords: { + type: 'number' + }, + maxlength: { + type: 'number' + }, + threshold: { + type: 'number' + } + }, anyOf: [{ required: ['maxwords'], errorMessage: 'Either "maxlength" or "maxwords" must be provided' @@ -1152,7 +1232,7 @@ return; } const $target = document.getElementById(targetId); - if ($target && $target.classList.contains('govuk-checkboxes__conditional')) { + if ($target != null && $target.classList.contains('govuk-checkboxes__conditional')) { const inputIsChecked = $input.checked; $input.setAttribute('aria-expanded', inputIsChecked.toString()); $target.classList.toggle('govuk-checkboxes__conditional--hidden', !inputIsChecked); @@ -1225,7 +1305,7 @@ }); } this.$module = $module; - this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset($module.dataset)); + this.config = mergeConfigs(ErrorSummary.defaults, config, normaliseDataset(ErrorSummary, $module.dataset)); if (!this.config.disableAutoFocus) { setFocus(this.$module); } @@ -1290,10 +1370,21 @@ * @property {boolean} [disableAutoFocus=false] - If set to `true` the error * summary will not be focussed when the page loads. */ + + /** + * @typedef {import('../../common/index.mjs').Schema} Schema + */ ErrorSummary.moduleName = 'govuk-error-summary'; ErrorSummary.defaults = Object.freeze({ disableAutoFocus: false }); + ErrorSummary.schema = Object.freeze({ + properties: { + disableAutoFocus: { + type: 'boolean' + } + } + }); /** * Exit this page component @@ -1336,8 +1427,8 @@ identifier: 'Button (`.govuk-exit-this-page__button`)' }); } - this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset($module.dataset)); - this.i18n = new I18n(extractConfigByNamespace(this.config, 'i18n')); + this.config = mergeConfigs(ExitThisPage.defaults, config, normaliseDataset(ExitThisPage, $module.dataset)); + this.i18n = new I18n(this.config.i18n); this.$module = $module; this.$button = $button; const $skiplinkButton = document.querySelector('.govuk-js-exit-this-page-skiplink'); @@ -1407,7 +1498,7 @@ if (!this.$updateSpan) { return; } - if ((event.key === 'Shift' || event.keyCode === 16 || event.which === 16) && !this.lastKeyWasModified) { + if (event.key === 'Shift' && !this.lastKeyWasModified) { this.keypressCounter += 1; this.updateIndicator(); if (this.timeoutMessageId) { @@ -1501,6 +1592,10 @@ * @property {string} [pressOneMoreTime] - Screen reader announcement informing * the user they must press the activation key one more time. */ + + /** + * @typedef {import('../../common/index.mjs').Schema} Schema + */ ExitThisPage.moduleName = 'govuk-exit-this-page'; ExitThisPage.defaults = Object.freeze({ i18n: { @@ -1510,6 +1605,13 @@ pressOneMoreTime: 'Shift, press 1 more time to exit.' } }); + ExitThisPage.schema = Object.freeze({ + properties: { + i18n: { + type: 'object' + } + } + }); /** * Header component @@ -1624,7 +1726,7 @@ }); } this.$module = $module; - this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset($module.dataset)); + this.config = mergeConfigs(NotificationBanner.defaults, config, normaliseDataset(NotificationBanner, $module.dataset)); if (this.$module.getAttribute('role') === 'alert' && !this.config.disableAutoFocus) { setFocus(this.$module); } @@ -1640,10 +1742,175 @@ * 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. */ + + /** + * @typedef {import('../../common/index.mjs').Schema} Schema + */ NotificationBanner.moduleName = 'govuk-notification-banner'; NotificationBanner.defaults = Object.freeze({ disableAutoFocus: false }); + NotificationBanner.schema = Object.freeze({ + properties: { + disableAutoFocus: { + type: 'boolean' + } + } + }); + + /** + * Password input component + * + * @preserve + */ + class PasswordInput extends GOVUKFrontendComponent { + /** + * @param {Element | null} $module - HTML element to use for password input + * @param {PasswordInputConfig} [config] - Password input config + */ + constructor($module, config = {}) { + super(); + this.$module = void 0; + this.config = void 0; + this.i18n = void 0; + this.$input = void 0; + this.$showHideButton = void 0; + this.$screenReaderStatusMessage = void 0; + if (!($module instanceof HTMLElement)) { + throw new ElementError({ + componentName: 'Password input', + element: $module, + identifier: 'Root element (`$module`)' + }); + } + const $input = $module.querySelector('.govuk-js-password-input-input'); + if (!($input instanceof HTMLInputElement)) { + throw new ElementError({ + componentName: 'Password input', + element: $input, + expectedType: 'HTMLInputElement', + identifier: 'Form field (`.govuk-js-password-input-input`)' + }); + } + if ($input.type !== 'password') { + throw new ElementError('Password input: Form field (`.govuk-js-password-input-input`) must be of type `password`.'); + } + const $showHideButton = $module.querySelector('.govuk-js-password-input-toggle'); + if (!($showHideButton instanceof HTMLButtonElement)) { + throw new ElementError({ + componentName: 'Password input', + element: $showHideButton, + expectedType: 'HTMLButtonElement', + identifier: 'Button (`.govuk-js-password-input-toggle`)' + }); + } + if ($showHideButton.type !== 'button') { + throw new ElementError('Password input: Button (`.govuk-js-password-input-toggle`) must be of type `button`.'); + } + this.$module = $module; + this.$input = $input; + this.$showHideButton = $showHideButton; + this.config = mergeConfigs(PasswordInput.defaults, config, normaliseDataset(PasswordInput, $module.dataset)); + this.i18n = new I18n(this.config.i18n, { + locale: closestAttributeValue($module, 'lang') + }); + this.$showHideButton.removeAttribute('hidden'); + const $screenReaderStatusMessage = document.createElement('div'); + $screenReaderStatusMessage.className = 'govuk-password-input__sr-status govuk-visually-hidden'; + $screenReaderStatusMessage.setAttribute('aria-live', 'polite'); + this.$screenReaderStatusMessage = $screenReaderStatusMessage; + this.$input.insertAdjacentElement('afterend', $screenReaderStatusMessage); + this.$showHideButton.addEventListener('click', this.toggle.bind(this)); + if (this.$input.form) { + this.$input.form.addEventListener('submit', () => this.hide()); + } + window.addEventListener('pageshow', event => { + if (event.persisted && this.$input.type !== 'password') { + this.hide(); + } + }); + this.hide(); + } + toggle(event) { + event.preventDefault(); + if (this.$input.type === 'password') { + this.show(); + return; + } + this.hide(); + } + show() { + this.setType('text'); + } + hide() { + this.setType('password'); + } + setType(type) { + if (type === this.$input.type) { + return; + } + this.$input.setAttribute('type', type); + const isHidden = type === 'password'; + const prefixButton = isHidden ? 'show' : 'hide'; + const prefixStatus = isHidden ? 'passwordHidden' : 'passwordShown'; + this.$showHideButton.innerText = this.i18n.t(`${prefixButton}Password`); + this.$showHideButton.setAttribute('aria-label', this.i18n.t(`${prefixButton}PasswordAriaLabel`)); + this.$screenReaderStatusMessage.innerText = this.i18n.t(`${prefixStatus}Announcement`); + } + } + + /** + * Password input config + * + * @typedef {object} PasswordInputConfig + * @property {PasswordInputTranslations} [i18n=PasswordInput.defaults.i18n] - Password input translations + */ + + /** + * Password input translations + * + * @see {@link PasswordInput.defaults.i18n} + * @typedef {object} PasswordInputTranslations + * + * Messages displayed to the user indicating the state of the show/hide toggle. + * @property {string} [showPassword] - Visible text of the button when the + * password is currently hidden. Plain text only. + * @property {string} [hidePassword] - Visible text of the button when the + * password is currently visible. Plain text only. + * @property {string} [showPasswordAriaLabel] - aria-label of the button when + * the password is currently hidden. Plain text only. + * @property {string} [hidePasswordAriaLabel] - aria-label of the button when + * the password is currently visible. Plain text only. + * @property {string} [passwordShownAnnouncement] - Screen reader + * announcement to make when the password has just become visible. + * Plain text only. + * @property {string} [passwordHiddenAnnouncement] - Screen reader + * announcement to make when the password has just been hidden. + * Plain text only. + */ + + /** + * @typedef {import('../../common/index.mjs').Schema} Schema + * @typedef {import('../../i18n.mjs').TranslationPluralForms} TranslationPluralForms + */ + PasswordInput.moduleName = 'govuk-password-input'; + PasswordInput.defaults = Object.freeze({ + i18n: { + showPassword: 'Show', + hidePassword: 'Hide', + showPasswordAriaLabel: 'Show password', + hidePasswordAriaLabel: 'Hide password', + passwordShownAnnouncement: 'Your password is visible', + passwordHiddenAnnouncement: 'Your password is hidden' + } + }); + PasswordInput.schema = Object.freeze({ + properties: { + i18n: { + type: 'object' + } + } + }); /** * Radios component @@ -1812,12 +2079,6 @@ this.$tabs = void 0; this.$tabList = void 0; this.$tabListItems = void 0; - this.keys = { - left: 37, - right: 39, - up: 38, - down: 40 - }; this.jsHiddenClass = 'govuk-tabs__panel--hidden'; this.changingHash = false; this.boundTabClick = void 0; @@ -1997,14 +2258,18 @@ $panel.id = panelId; } onTabKeydown(event) { - switch (event.keyCode) { - case this.keys.left: - case this.keys.up: + switch (event.key) { + case 'ArrowLeft': + case 'ArrowUp': + case 'Left': + case 'Up': this.activatePreviousTab(); event.preventDefault(); break; - case this.keys.right: - case this.keys.down: + case 'ArrowRight': + case 'ArrowDown': + case 'Right': + case 'Down': this.activateNextTab(); event.preventDefault(); break; @@ -2104,7 +2369,7 @@ console.log(new SupportError()); return; } - const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [Radios], [SkipLink], [Tabs]]; + const components = [[Accordion, config.accordion], [Button, config.button], [CharacterCount, config.characterCount], [Checkboxes], [ErrorSummary, config.errorSummary], [ExitThisPage, config.exitThisPage], [Header], [NotificationBanner, config.notificationBanner], [PasswordInput, config.passwordInput], [Radios], [SkipLink], [Tabs]]; const $scope = (_config$scope = config.scope) != null ? _config$scope : document; components.forEach(([Component, config]) => { const $elements = $scope.querySelectorAll(`[data-module="${Component.moduleName}"]`); @@ -2128,6 +2393,7 @@ * @property {ErrorSummaryConfig} [errorSummary] - Error Summary config * @property {ExitThisPageConfig} [exitThisPage] - Exit This Page config * @property {NotificationBannerConfig} [notificationBanner] - Notification Banner config + * @property {PasswordInputConfig} [passwordInput] - Password input config */ /** @@ -2142,6 +2408,7 @@ * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageConfig} ExitThisPageConfig * @typedef {import('./components/exit-this-page/exit-this-page.mjs').ExitThisPageTranslations} ExitThisPageTranslations * @typedef {import('./components/notification-banner/notification-banner.mjs').NotificationBannerConfig} NotificationBannerConfig + * @typedef {import('./components/password-input/password-input.mjs').PasswordInputConfig} PasswordInputConfig */ /** @@ -2158,6 +2425,7 @@ exports.ExitThisPage = ExitThisPage; exports.Header = Header; exports.NotificationBanner = NotificationBanner; + exports.PasswordInput = PasswordInput; exports.Radios = Radios; exports.SkipLink = SkipLink; exports.Tabs = Tabs; diff --git a/app/assets/scss/app.scss b/app/assets/scss/app.scss index 8c6578bf..0693b95e 100644 --- a/app/assets/scss/app.scss +++ b/app/assets/scss/app.scss @@ -7,12 +7,12 @@ $govuk-global-styles: true; @import 'node_modules/govuk-frontend/dist/govuk/all'; -@import 'node_modules/dfe-frontend-alpha/packages/dfefrontend'; +@import 'node_modules/dfe-webfrontend/packages/dfefrontend'; @import './ncs/index.scss'; @import './_hero.scss'; @import './_topnav.scss'; @import './_banner.scss'; -@import './_cards.scss'; +// @import './_cards.scss'; @import './_listpanels.scss'; @import './_subnav.scss'; @import './_feedback.scss'; @@ -1992,32 +1992,6 @@ code { } - - -.dfe-header { - border-bottom: none; -} - -.dfe-header__navigation { - - background: #003a69; -} - -.dfe-header__navigation-item--current { - border-bottom: 6px solid $color_dfe-secondary-blue; -} - -.dfe-header__navigation-item.dfe-header__navigation-item--current { - box-shadow: none !important; -} - - - -.dfe-header__search *, -.dfe-header__search>* { - border-radius: 0px !important; -} - .dfe-manual-cta-card { padding: 25px; border-radius: 2px; @@ -2464,21 +2438,3 @@ ol { } // DfE Frontend Overrides - until a future release - -.dfe-header__menu-toggle { - border-radius: 0px; -} - -@include govuk-media-query($until: large-desktop) { - .dfe-header__navigation { - background: #ffffff; - } - - .dfe-header__navigation-item--current { - a.dfe-header__navigation-link { - color: #003a69; - } - } - -} - diff --git a/app/controllers/designSystemController.js b/app/controllers/designSystemController.js index 734b68e9..b8e10731 100644 --- a/app/controllers/designSystemController.js +++ b/app/controllers/designSystemController.js @@ -13,7 +13,7 @@ exports.get_index = async function (req, res) { exports.get_frontend = async function (req, res) { - const packageName = 'dfe-frontend-alpha'; + const packageName = 'dfe-webfrontend'; axios .get(`https://registry.npmjs.org/${packageName}`) .then((response) => { diff --git a/app/views/.DS_Store b/app/views/.DS_Store index e4711a31ca0010dc8fcf4dbe43b51b181acbcf62..108e529c1297e1b93be641ef1cea6b339b91f84b 100644 GIT binary patch delta 706 zcmZn(XbIS0DZsdV@;m{#$q@p3*aFy=`5T^{EG4J{W4Z|XF%|MmJ|HMHd7U5^8>i`V zkb-@JY6zy3KtYCKaB_Zb0RtFpV3@p2NKPOp9Z8V&L>5SJa)J;KTmSlfATyQ-DG24} zr?@2Lw`pRuC4M{6ffpO)X_VNU@x-5`rl)T**dPLv?+Mqj`RVn5^iB~$wFoZk&&A>>6Xij0 zV>HgVgUmSU2y`JULk>eKLn1>FLmslbAu1+s5p`nP7dKf!Ol&fr7#Ew2`bm(9a$-sd zCeYnte2gC^XNv{0?LB-3B>zE7X7W8T9t5|9t@F}ZkW7}i3XC~L+?ko-|NF_e#HH9a zX`Khj{t}mgF=Zs=7%xn=mQZ3$oE#@1rz1PzB2Wd`#q12F3A>> z6Xij0V>I?n1etNv5$Hlzh8%`ehD3%UhCF0 diff --git a/app/views/design-system/components/card/examples/everything.html b/app/views/design-system/components/card/examples/everything.html new file mode 100644 index 00000000..4a144c18 --- /dev/null +++ b/app/views/design-system/components/card/examples/everything.html @@ -0,0 +1,100 @@ +{% extends "layouts/example.html" %} + + +{% block content %} + + + + + + {% endblock %} \ No newline at end of file diff --git a/app/views/design-system/components/card/index.html b/app/views/design-system/components/card/index.html new file mode 100644 index 00000000..ea940eac --- /dev/null +++ b/app/views/design-system/components/card/index.html @@ -0,0 +1,105 @@ +{% extends "layouts/content-full-width.html" %} + +{% set pageName = "Card" %} +{% set pageDescription = "Use the DfE card component when you need to group content in a visually appealing way." %} +{% set selectedNav = "Design system" %} +{% set parentSection = "Components"%} +{% set isChildSection = "true"%} +{% set isSubNavSection = "true" %} +{% set lastUpdated = "" %} +{% set backlog_issue_id = "13" %} + + +{% block hero %} +
+
+
+
    +
  1. + Home +
  2. +
  3. + Design system +
  4. +
  5. + Components +
  6. +
  7. + Card +
  8. +
+
+
+
+

{{pageName}}

+

{{pageDescription}}

+ +
+
+
+
+{% endblock %} + + +{% block content %} +
+
+
+ {% include "includes/_side-nav.html"%} +
+
+ +

Guidance

+ +

The card component is a versatile user interface element used to display grouped information in a visually appealing and organised way.

+

They can enhance user interaction and content presentation across various applications and devices.

+ + +

Test as plain content first

+

Cards add additional load to the page, so before using them, test your content as plain content.

+ + +

Services using this component

+ +

The following services are using this component:

+ + + +

Help us improve this component

+ +

To improve this component based on user needs and research, you can contribute on the Card - Component #13 project issue.

+
+
+
+{% endblock %} \ No newline at end of file diff --git a/app/views/design-system/components/card/properties.html b/app/views/design-system/components/card/properties.html new file mode 100644 index 00000000..9ce84684 --- /dev/null +++ b/app/views/design-system/components/card/properties.html @@ -0,0 +1,189 @@ +{% extends "layouts/content-full-width.html" %} + +{% set pageName = "Card" %} +{% set pageDescription = "Use the DfE card component when you need to group content in a visually appealing way." %} +{% set selectedNav = "Design system" %} +{% set parentSection = "Components"%} +{% set isChildSection = "true"%} +{% set isSubNavSection = "true" %} +{% set lastUpdated = "" %} +{% set backlog_issue_id = "13" %} + + +{% block hero %} +
+
+
+
    +
  1. + Home +
  2. +
  3. + Design system +
  4. +
  5. + Components +
  6. +
  7. + Card +
  8. +
+
+
+
+

{{pageName}}

+

{{pageDescription}}

+ +
+
+
+
+{% endblock %} + + +{% block content %} +
+
+
+ {% include "includes/_side-nav.html"%} +
+
+ +

Properties

+ +

Details of the properties that you can configure when using the nunjucks version of this component.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Parameter NameTypeDescription
showNavBooleanControls whether the navigation menu is displayed.
showSearchBooleanControls whether the search functionality is displayed.
showUserNameBooleanControls whether the username or user-related information is displayed.
showHeaderActionLinksBooleanControls whether the header action links are displayed.
logoPathStringSpecifies the path to the logo images used in the header.
logoAltTextStringProvides the alternative text for the logo images.
classesStringAdditional CSS classes that might be added to the header element.
attributesObjectAdditional HTML attributes for the header element, handled in a loop for each + attribute-value pair.
homeHrefStringSpecifies the href for the homepage link associated with the logo.
ariaLabelStringAccessibility label for the logo link, providing a text alternative for screen + readers if not set, defaults to "DfE homepage".
headerActionLinksArrayAn array of objects where each object may contain `url` and `label` for action + links in the header.
searchActionStringSpecifies the URL for the POST action on the search form.
searchInputNameStringSpecifies the name attribute for the search input, defaulting to + "search-field" if not provided.
serviceObjectAn object containing `serviceUrl` and the `name` of the service.
primaryLinksArrayAn array of objects for primary navigation links, where each object contains + `url` and `label`.
selectedNavStringA parameter used to mark the current navigation item as active.
+ + + + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/app/views/design-system/components/card/variants.html b/app/views/design-system/components/card/variants.html new file mode 100644 index 00000000..0f137673 --- /dev/null +++ b/app/views/design-system/components/card/variants.html @@ -0,0 +1,919 @@ +{% extends "layouts/content-full-width.html" %} + +{% set pageName = "Card" %} +{% set pageDescription = "Use the DfE card component when you need to group content in a visually appealing way." %} +{% set selectedNav = "Design system" %} +{% set parentSection = "Components"%} +{% set isChildSection = "true"%} +{% set isSubNavSection = "true" %} +{% set lastUpdated = "" %} +{% set backlog_issue_id = "13" %} + + +{% block hero %} +
+
+
+
    +
  1. + Home +
  2. +
  3. + Design system +
  4. +
  5. + Components +
  6. +
  7. + Card +
  8. +
+
+
+
+

{{pageName}}

+

{{pageDescription}}

+ +
+
+
+
+{% endblock %} + + +{% block content %} +
+
+
+ {% include "includes/_side-nav.html"%} +
+
+ +
+

All features

+ +

An example of the header component with all features enabled. See other variants.

+ +
+ +
+

+ Contents +

+ +
+

+ Open + example in a new window +

+ + + + + +
+
+ +
<header class="dfe-header" role="banner">
+  <div class="dfe-width-container dfe-header__container">
+    <div class="dfe-header__logo">
+      <a class="dfe-header__link dfe-header__link--service " href="#" aria-label="DfE homepage">
+      <img src="/assets/images/dfe-logo.png" class="dfe-logo" alt="DfE Homepage">
+      <img src="/assets/images/dfe-logo-alt.png" class="dfe-logo-hover" alt="DfE Homepage">
+      <span class="dfe-header__service-name">
+      Service name
+      </span>
+      </a>
+    </div>
+    <div class="dfe-header__content" id="content-header">
+      <ul class="dfe-header__action-links">
+        <li>
+          <a href="/" class="govuk-link govuk-link--inverse">My account</a>
+        </li>
+        <li>
+          <a href="/" class="govuk-link govuk-link--inverse">Sign out</a>
+        </li>
+      </ul>
+      <div class="dfe-header__menu">
+        <button class="dfe-header__menu-toggle" id="toggle-menu" aria-controls="header-navigation" aria-expanded="false">Menu</button>
+      </div>
+      <div class="dfe-header__search">
+        <button class="dfe-header__search-toggle" id="toggle-search" aria-controls="search" aria-label="Open search">
+          <svg class="dfe-icon dfe-icon__search" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="27" height="27">
+            <path d="M19.71 18.29l-4.11-4.1a7 7 0 1 0-1.41 1.41l4.1 4.11a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42zM5 10a5 5 0 1 1 5 5 5 5 0 0 1-5-5z"></path>
+          </svg>
+          <span class="govuk-visually-hidden">Search</span>
+        </button>
+        <div class="dfe-header__search-wrap" id="wrap-search">
+          <form class="dfe-header__search-form" id="search" action="/search/" method="get" role="search">
+            <label class="govuk-visually-hidden" for="searchterm">Search this website</label>
+            <input class="dfe-search__input" id="searchterm" name="searchterm" type="search" placeholder="Search" autocomplete="off">
+            <button class="dfe-search__submit" type="submit">
+              <svg class="dfe-icon dfe-icon__search" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="27" height="27">
+                <path d="M19.71 18.29l-4.11-4.1a7 7 0 1 0-1.41 1.41l4.1 4.11a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42zM5 10a5 5 0 1 1 5 5 5 5 0 0 1-5-5z"></path>
+              </svg>
+              <span class="govuk-visually-hidden">Search</span>
+            </button>
+            <button class="dfe-search__close" id="close-search">
+              <svg class="dfe-icon dfe-icon__close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="27" height="27">
+                <path d="M13.41 12l5.3-5.29a1 1 0 1 0-1.42-1.42L12 10.59l-5.29-5.3a1 1 0 0 0-1.42 1.42l5.3 5.29-5.3 5.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l5.29-5.3 5.29 5.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"></path>
+              </svg>
+              <span class="govuk-visually-hidden">Close search</span>
+            </button>
+          </form>
+        </div>
+      </div>
+    </div>
+  </div>
+  <nav class="dfe-header__navigation" id="header-navigation" role="navigation" aria-label="Primary navigation" aria-labelledby="label-navigation">
+    <div class="dfe-width-container">
+      <p class="dfe-header__navigation-title">
+        <span id="label-navigation">Menu</span>
+        <button class="dfe-header__navigation-close" id="close-menu">
+          <svg class="dfe-icon dfe-icon__close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="27" height="27">
+            <path d="M13.41 12l5.3-5.29a1 1 0 1 0-1.42-1.42L12 10.59l-5.29-5.3a1 1 0 0 0-1.42 1.42l5.3 5.29-5.3 5.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l5.29-5.3 5.29 5.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"></path>
+          </svg>
+          <span class="govuk-visually-hidden">Close menu</span>
+        </button>
+      </p>
+      <ul class="dfe-header__navigation-list">
+        <li class="dfe-header__navigation-item dfe-header__navigation-item--current">
+          <a class="dfe-header__navigation-link" href="#">
+            Navigation link 1 
+            <svg class="dfe-icon dfe-icon__chevron-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" width="34" height="34">
+              <path d="M15.5 12a1 1 0 0 1-.29.71l-5 5a1 1 0 0 1-1.42-1.42l4.3-4.29-4.3-4.29a1 1 0 0 1 1.42-1.42l5 5a1 1 0 0 1 .29.71z"></path>
+            </svg>
+          </a>
+        </li>
+        <li class="dfe-header__navigation-item ">
+          <a class="dfe-header__navigation-link" href="#">
+            Navigation link 2
+            <svg class="dfe-icon dfe-icon__chevron-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" width="34" height="34">
+              <path d="M15.5 12a1 1 0 0 1-.29.71l-5 5a1 1 0 0 1-1.42-1.42l4.3-4.29-4.3-4.29a1 1 0 0 1 1.42-1.42l5 5a1 1 0 0 1 .29.71z"></path>
+            </svg>
+          </a>
+        </li>
+        <li class="dfe-header__navigation-item ">
+          <a class="dfe-header__navigation-link" href="#">
+            Navigation link 3
+            <svg class="dfe-icon dfe-icon__chevron-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" width="34" height="34">
+              <path d="M15.5 12a1 1 0 0 1-.29.71l-5 5a1 1 0 0 1-1.42-1.42l4.3-4.29-4.3-4.29a1 1 0 0 1 1.42-1.42l5 5a1 1 0 0 1 .29.71z"></path>
+            </svg>
+          </a>
+        </li>
+      </ul>
+    </div>
+  </nav>
+</header>
+
+
+ {% raw %} +
{% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
+
+{{ header({
+    "showNav": "true",
+    "showSearch": "true",
+    "showHeaderActionLinks": "true",
+    "logoPath": "public/images",
+    "searchAction": "/search/",
+    "searchInputName": "q",
+    "service": {
+      "name": "Service name"
+    },
+    "headerActionLinks": [
+      {
+        "url"  : "/",
+        "label" : "My account"
+      },
+      {
+        "url"  : "/",
+        "label" : "Sign out"
+      }
+    ],
+    "primaryLinks": [
+        {
+        "url"  : "#",
+        "label" : "Navigation link 1"
+      }, {
+        "url"  : "#",
+        "label" : "Navigation link 2"
+      }, {
+        "url"  : "#",
+        "label" : "Navigation link 3"
+      }
+    ]
+  })
+}}
+ {% endraw%} +
+
+
+ + + + +

Variants

+ +

Service name only

+ +

Use this header for small services and sites which have fewer than 5 pages.

+ +
+ +
+

+ Contents +

+ + +
+ +
<header class="dfe-header" role="banner">
+   <div class="dfe-width-container dfe-header__container">
+      <div class="dfe-header__logo">
+         <a class="dfe-header__link dfe-header__link--service " href="#" aria-label="DfE homepage">
+         <img src="/assets/images/dfe-logo.png" class="dfe-logo" alt="DfE Homepage">
+         <img src="/assets/images/dfe-logo-alt.png" class="dfe-logo-hover" alt="DfE Homepage">
+         <span class="dfe-header__service-name">
+         Service name
+         </span>
+         </a>
+      </div>
+   </div>
+</header>
+
+
+ {% raw %} +
{% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
+
+{{ header({
+    "showNav": "false",
+    "showSearch": "false",
+    "showUserName": "false",
+    "userName": "",
+    "userNameURL": "",
+    "logoPath": "public/images",
+    "service": {
+      "name": "Service name"
+    }
+  })
+}}
+ {% endraw%} +
+
+
+ +
+

With search

+ +

Use this header if your service or website needs a search function.

+ +

You can specify the URL to post the search form to, by default it's /search.

+
+ +
+

+ Contents +

+ +
+

+ Open example in + a new window +

+ + +
+
+ +
<header class="dfe-header" role="banner">
+   <div class="dfe-width-container dfe-header__container">
+      <div class="dfe-header__logo">
+         <a class="dfe-header__link dfe-header__link--service " href="#" aria-label="DfE homepage">
+         <img src="/assets/images/dfe-logo.png" class="dfe-logo" alt="DfE Homepage">
+         <img src="/assets/images/dfe-logo-alt.png" class="dfe-logo-hover" alt="DfE Homepage">
+         <span class="dfe-header__service-name">
+         Service name
+         </span>
+         </a>
+      </div>
+      <div class="dfe-header__content" id="content-header">
+         <div class="dfe-header__search">
+            <button class="dfe-header__search-toggle" id="toggle-search" aria-controls="search" aria-label="Open search">
+               <svg class="dfe-icon dfe-icon__search" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="27" height="27">
+                  <path d="M19.71 18.29l-4.11-4.1a7 7 0 1 0-1.41 1.41l4.1 4.11a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42zM5 10a5 5 0 1 1 5 5 5 5 0 0 1-5-5z"></path>
+               </svg>
+               <span class="govuk-visually-hidden">Search</span>
+            </button>
+            <div class="dfe-header__search-wrap" id="wrap-search">
+               <form class="dfe-header__search-form" id="search" action="/search/" method="get" role="search">
+                  <label class="govuk-visually-hidden" for="searchterm">Search this website</label>
+                  <input class="dfe-search__input" id="searchterm" name="searchterm" type="search" placeholder="Search" autocomplete="off">
+                  <button class="dfe-search__submit" type="submit">
+                     <svg class="dfe-icon dfe-icon__search" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="27" height="27">
+                        <path d="M19.71 18.29l-4.11-4.1a7 7 0 1 0-1.41 1.41l4.1 4.11a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42zM5 10a5 5 0 1 1 5 5 5 5 0 0 1-5-5z"></path>
+                     </svg>
+                     <span class="govuk-visually-hidden">Search</span>
+                  </button>
+                  <button class="dfe-search__close" id="close-search">
+                     <svg class="dfe-icon dfe-icon__close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false" width="27" height="27">
+                        <path d="M13.41 12l5.3-5.29a1 1 0 1 0-1.42-1.42L12 10.59l-5.29-5.3a1 1 0 0 0-1.42 1.42l5.3 5.29-5.3 5.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l5.29-5.3 5.29 5.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"></path>
+                     </svg>
+                     <span class="govuk-visually-hidden">Close search</span>
+                  </button>
+               </form>
+            </div>
+         </div>
+      </div>
+   </div>
+</header>
+
+
+ {% raw %} +
{% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
+
+{{ header({
+    "showNav": "false",
+    "showSearch": "true",
+    "showUserName": "false",
+    "userName": "",
+    "userNameURL": "",
+    "logoPath": "public/images",
+    "searchAction": "/search/",
+    "searchInputName": "searchterm",
+    "service": {
+      "name": "Service name"
+    }
+  })
+}}
+ {% endraw%} +
+
+
+ + + +
+

With account option

+ +

Use this header if you want to have account options for accessing an account page or sign out of a + service.

+ +
+

+ Contents +

+ +
+

+ Open example in + a new window +

+ + +
+
+ +
<header class="dfe-header" role="banner">
+    <div class="dfe-width-container dfe-header__container">
+      <div class="dfe-header__logo">
+        <a class="dfe-header__link dfe-header__link--service " href="#" aria-label="DfE homepage">
+          <img src="/assets/images/dfe-logo.png" class="dfe-logo" alt="DfE Homepage">
+          <img src="/assets/images/dfe-logo-alt.png" class="dfe-logo-hover" alt="DfE Homepage">
+          <span class="dfe-header__service-name">
+         Service name
+         </span>
+        </a>
+      </div>
+     <div class="dfe-header__content" id="content-header">
+         <ul class="dfe-header__action-links">
+            <li>
+               Name
+            </li>
+            <li>
+               <a href="/sign-out" class="govuk-link govuk-link--inverse">Sign out</a>
+            </li>
+         </ul>
+      </div>
+    </div>
+  </header>
+
+
+ {% raw %} +
{% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
+
+{{ header({
+    "showNav": "false",
+    "showSearch": "false",
+    "showHeaderActionLinks": "true",
+    "userNameURL": "#",
+    "logoPath": "public/images",
+    "service": {
+      "name": "Service name"
+    },
+    "headerActionLinks": [
+      {
+        "url"  : "",
+        "label" : "Name"
+      },
+      {
+        "url"  : "#",
+        "label" : "Sign out"
+      }
+    ]
+  })
+}}
+ {% endraw%} +
+
+
+ + +
+
+
+{% endblock %} \ No newline at end of file diff --git a/app/views/design-system/components/header/examples/everything.html b/app/views/design-system/components/header/examples/everything.html index 4a144c18..74565c4c 100644 --- a/app/views/design-system/components/header/examples/everything.html +++ b/app/views/design-system/components/header/examples/everything.html @@ -1,100 +1,46 @@ {% extends "layouts/example.html" %} - +{% set selectedNav = "Navigation link 1"%} {% block content %} - +{{ header({ +"showNav": "true", +"showSearch": "true", +"showHeaderActionLinks": "true", +"logoPath": "assets/images", +"logoAltText": "Department for Education", +"searchAction": "/search/", +"searchInputName": "q", +"selectedNav": selectedNav, +"service": { +"name": "Service name", +"serviceUrl": "/d" +}, +"headerActionLinks": [ +{ +"url" : "/", +"label" : "My account" +}, +{ +"url" : "/", +"label" : "Sign out" +} +], +"primaryLinks": [ +{ +"url" : "#", +"label" : "Navigation link 1" +}, { +"url" : "#", +"label" : "Navigation link 2" +}, { +"url" : "#", +"label" : "Navigation link 3" +} +] +}) +}} - {% endblock %} \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/app/views/design-system/components/header/index.html b/app/views/design-system/components/header/index.html index 32e5f874..5066db36 100644 --- a/app/views/design-system/components/header/index.html +++ b/app/views/design-system/components/header/index.html @@ -22,7 +22,10 @@ Design system
  • - Styles + Components +
  • +
  • + Header
  • diff --git a/app/views/design-system/components/header/properties.html b/app/views/design-system/components/header/properties.html index c441ce64..56f2c3a1 100644 --- a/app/views/design-system/components/header/properties.html +++ b/app/views/design-system/components/header/properties.html @@ -22,7 +22,10 @@ Design system
  • - Styles + Components +
  • +
  • + Header
  • @@ -33,13 +36,14 @@

    {{pageName}}

    @@ -66,161 +70,118 @@

    Properties

    - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeRequiredDescriptionParameter NameTypeDescription
    - showNav - booleanYesSet to "true" to show the navigation links in the header.
    - showSearch - booleanYesSet to "true" to show the site search input form.
    - showHeaderActionLinks - booleanYesSet to "true" to show list if items in the top right of page.
    - homeHref - stringNoThe href of the link for the logo and mobile home link in the navigation - links. Defaults to "/".
    - ariaLabel - stringNoAria label for the logo href. Defaults to "DfE homepage".
    - logoPath - stringYesSet this to the local path for images. In version 13 of the prototype kit, - set this is 'public/images'.
    - selectedNav - stringNoPass in a value from the view which matches the - primaryLinks.label and it will add the class - dfe-header__navigation-item--current to the corresponding nav item. -
    - headerActionLinks - arrayNoArray of links for use in the header on the top right.
    - headerActionLinks[].url - stringNoThe href of a navigation item in the header. If blank, it will just display - the label
    - headerActionLinks[].label - stringNoThe label of the item in the header.
    - primaryLinks - arrayNoArray of navigation links for use in the header.
    - primaryLinks[].url - stringNoThe href of a navigation item in the header.
    - primaryLinks[].label - stringNoThe label of a navigation item in the header.
    - service - objectNoObject containing the *name* of the service.
    - attributes - objectNoAny extra HTML attributes (for example data attributes) to add to the header - container.
    - searchAction - stringNoThe search action endpoint. Defaults to "/search".
    - searchInputName - stringNoThe name for the search field. Defaults to "searchterm".showNavBooleanControls whether the navigation menu is displayed.
    showSearchBooleanControls whether the search functionality is displayed.
    showUserNameBooleanControls whether the username or user-related information is displayed.
    showHeaderActionLinksBooleanControls whether the header action links are displayed.
    logoPathStringSpecifies the path to the logo images used in the header.
    logoAltTextStringProvides the alternative text for the logo images.
    classesStringAdditional CSS classes that might be added to the header element.
    attributesObjectAdditional HTML attributes for the header element, handled in a loop for each + attribute-value pair.
    homeHrefStringSpecifies the href for the homepage link associated with the logo.
    ariaLabelStringAccessibility label for the logo link, providing a text alternative for screen + readers if not set, defaults to "DfE homepage".
    headerActionLinksArrayAn array of objects where each object contains `url` and `label` for action + links in the header.
    searchActionStringSpecifies the URL for the POST action on the search form.
    searchInputNameStringSpecifies the name attribute for the search input, defaulting to + "search-field" if not provided.
    serviceObjectAn object containing `serviceUrl` and the `name` of the service.
    primaryLinksArrayAn array of objects for primary navigation links, where each object contains + `url` and `label`.
    selectedNavStringA parameter used to mark the current navigation item as active.
    - + diff --git a/app/views/design-system/components/header/variants.html b/app/views/design-system/components/header/variants.html index fd18bf61..b40f3ce5 100644 --- a/app/views/design-system/components/header/variants.html +++ b/app/views/design-system/components/header/variants.html @@ -36,7 +36,8 @@

    {{pageName}}

    Guidance
  • - Variants + Variants
  • Properties @@ -58,7 +59,7 @@

    {{pageName}}

    -
    +

    All features

    An example of the header component with all features enabled. See other example in a new window

    - - + sss
    @@ -284,13 +194,13 @@

    {% raw %} -
    {% from 'node_modules/dfe-frontend-alpha/packages/components/header/macro.njk' import header %}
    +              
    {% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
     
     {{ header({
         "showNav": "true",
         "showSearch": "true",
         "showHeaderActionLinks": "true",
    -    "logoPath": "assets",
    +    "logoPath": "public/images",
         "searchAction": "/search/",
         "searchInputName": "q",
         "service": {
    @@ -395,7 +305,7 @@ 

    {% raw %} -
    {% from 'node_modules/dfe-frontend-alpha/packages/components/header/macro.njk' import header %}
    +              
    {% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
     
     {{ header({
         "showNav": "false",
    @@ -403,7 +313,7 @@ 

    "showUserName": "false", "userName": "", "userNameURL": "", - "logoPath": "assets", + "logoPath": "public/images", "service": { "name": "Service name" } @@ -549,7 +459,7 @@

    {% raw %} -
    {% from 'node_modules/dfe-frontend-alpha/packages/components/header/macro.njk' import header %}
    +                
    {% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
     
     {{ header({
         "showNav": "false",
    @@ -557,7 +467,7 @@ 

    "showUserName": "false", "userName": "", "userNameURL": "", - "logoPath": "assets", + "logoPath": "public/images", "searchAction": "/search/", "searchInputName": "searchterm", "service": { @@ -739,7 +649,7 @@

    {% raw %} -
    {% from 'node_modules/dfe-frontend-alpha/packages/components/header/macro.njk' import header %}
    +                
    {% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
     
     {{ header({
         "showNav": "true",
    @@ -747,7 +657,7 @@ 

    "showUserName": "false", "userName": "", "userNameURL": "", - "logoPath": "assets", + "logoPath": "public/images", "service": { "name": "Service name" }, @@ -855,14 +765,14 @@

    {% raw %} -
    {% from 'node_modules/dfe-frontend-alpha/packages/components/header/macro.njk' import header %}
    +                
    {% from 'node_modules/dfe-webfrontend/packages/components/header/macro.njk' import header %}
     
     {{ header({
         "showNav": "false",
         "showSearch": "false",
         "showHeaderActionLinks": "true",
         "userNameURL": "#",
    -    "logoPath": "assets",
    +    "logoPath": "public/images",
         "service": {
           "name": "Service name"
         },
    @@ -883,7 +793,7 @@ 

    - + diff --git a/app/views/design-system/components/index.html b/app/views/design-system/components/index.html index 9e5b370d..eb668f25 100644 --- a/app/views/design-system/components/index.html +++ b/app/views/design-system/components/index.html @@ -24,7 +24,7 @@

  • - Styles + Components
  • @@ -58,14 +58,22 @@

    {{pageName}}

    Published components

    - +
    +
    +

    + Card +

    +

    Use the DfE card component when you need to group content in a visually appealing way.

    +
    +

    Header

    -

    Use the DfE header on internal services or those hosted on education.gov.uk

    +

    Use the DfE header on internal services or those hosted on education.gov.uk.

    @@ -88,8 +96,7 @@

    On our backlog

    target="_blank">Cards A card component is a box-like container used to display information neatly, featuring sections for text, images, and actions. - In - progress + Published Last release @@ -103,7 +103,8 @@

    Contributions

    DfE Frontend is managed by the DesignOps team and accepts contributions from the design community.

    -

    If you are building services and you create custom components or patterns which are not in the GOV.UK Design System, you must contribute these to the DfE Design System.

    +

    If you are building services and you create custom components or patterns which are not in the GOV.UK Design + System, you must contribute these to the DfE Design System.

    You should also contribute back to the GOV.UK Design System backlog.

    diff --git a/app/views/design-system/getting-started/index.html b/app/views/design-system/getting-started/index.html index 8b9d7a25..7ba8bd66 100644 --- a/app/views/design-system/getting-started/index.html +++ b/app/views/design-system/getting-started/index.html @@ -57,6 +57,11 @@

    {{pageName}}

    Follow the guidance for getting the prototype kit set up on your device before following these steps.

    +
    This guidance is accurate up to and including version 13.16.2 of the GOV.UK Prototype kit.
    + +

    If something doesn't work contact DesignOps for help so we can understand the + problem and improve guidance where possible.

    +

    Install the package

    DfE Frontend is available in the npm registry. You can install it using the npm install command in your @@ -68,16 +73,13 @@

    Install the package

    1. Install

    - npm install dfe-frontend-alpha + npm install dfe-webfrontend

    CSS, Javascript, and logo files

    -

    Add the following to the top of your application.scss file after the line -
    - @import 'node_modules/govuk-frontend/dist/govuk/all'; -

    +

    Go to the app/assets/sass folder and open the application.scss file

    @@ -85,7 +87,8 @@

    CSS, Javascript, and logo files

    2. application.scs

    - @import 'node_modules/dfe-frontend-alpha/packages/dfefrontend'; +

    Add this line to the top of the file. This needs to come before any other code you add to this file.

    + @import 'node_modules/dfe-webfrontend/packages/dfefrontend';
    @@ -94,16 +97,19 @@

    Javascript

    You need to include the javascript reference in your main layout. Find the scripts section and replace with the following code.

    +

    Go to the app/views/layouts folder and open the main.html file

    +
    -

    3. /views/layouts/main.html

    +

    3. main.html

    +

    Add this code to the bottom of the file.

    {%raw%}{% block scripts %}
       {{ super() }}
    -  <script src="/node_modules/dfe-frontend-alpha/dist/dfefrontend.min.js"></script>
    +  <script src="/node_modules/dfe-webfrontend/dist/dfefrontend.min.js"></script>
     {% endblock %}{%endraw%}
    @@ -119,13 +125,31 @@

    4. Copy image assets

    From

    - /node_modules/dfe-frontend-alpha/packages/assets/dfe-logo.png
    - /node_modules/dfe-frontend-alpha/packages/assets/dfe-logo-alt.png
    + /node_modules/dfe-webfrontend/packages/assets/dfe-logo.png
    + /node_modules/dfe-webfrontend/packages/assets/dfe-logo-alt.png

    To

    /app/assets/images
    +

    Run your app

    + +

    This is assuming you're using the GOV.UK prototype kit. You'll need to do your own checks on your app depending on your code and build process.

    + + +
    +
    +

    5. Run your app

    +
    +
    +

    In your terminal, run the command:

    + npm run dev +

    Your code should build, check for any messages, and your app should load at http://localhost:3000

    +
    +
    + + +

    You're good to go

    You will now be able to use DfE styles and components in your product.

    diff --git a/app/views/design-system/styles/typography/index.html b/app/views/design-system/styles/typography/index.html index ee5b6e61..d51e2af1 100644 --- a/app/views/design-system/styles/typography/index.html +++ b/app/views/design-system/styles/typography/index.html @@ -70,13 +70,12 @@

    {{pageName}}

    Overview

    By default, you must use the GOV Frontend - typography and GDS Transport typeface for sites on GOV.UK, except for the following - situations:

    + typography and GDS Transport typeface for sites on GOV.UK

    + +

    If your site is on education.gov.uk, you must use the inter typeface.

    +

    The only exception to this is where you are using education.gov.uk to host a service in private beta and will eventually be on service.gov.uk domain. You're fine to use the GDS Transport and GOV.UK header in this instance.

    -
      -
    • all internally-facing products for DfE staff
    • -
    • all products provided on the education.gov.uk domain
    • -
    +

    If you're unsure, contact DesignOps.

    To ensure your service meets the requirements @@ -106,7 +105,7 @@

    Typeface vs font

    -

    Import the typeface

    +

    Import the Inter typeface

    We are migrating to use the web-font Inter in our products and services that are not provided on GOV.UK.

    @@ -115,30 +114,24 @@

    Import the typeface

    adding the following imports to your SCSS/CSS files, otherwise you'll need to bundle the physical fonts in your build. Styles will drop to system fonts if the internet is not available.

    -
    -

    Inter is automatically included in Figma in the font options within the text panel.

    -
    -

    Configuration

    +

    Create a settings.scss file

    -

    Before all other imports, reference the Inter font.

    +
    This guidance is accurate up to and including version 13.16.2 of the GOV.UK Prototype kit.
    -
    -
    -

    scss/css file

    -
    -
    - @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); -
    -
    +

    If something doesn't work contact DesignOps for help so we can understand the + problem and improve guidance where possible.

    + +

    If it doesn't exist, create a settings.scss file in /app/assets/sass folder.

    -

    On the next line, after importing Inter, but before the inclusion of GOV Frontend styles

    -

    scss/css file

    +

    settings.scss file

    +

    Add the following code to the file.

    + @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); $govuk-font-family: 'Inter', sans-serif;
    @@ -146,8 +139,6 @@

    scss/css file

    All styles which use the GDS Transport typeface will now be replaced with the Inter typeface.

    You do not need to use different classes.

    - -