diff --git a/prettier.json b/prettier.json index 232464b5..11ac27e4 100644 --- a/prettier.json +++ b/prettier.json @@ -7,5 +7,6 @@ "bracketSpacing": true, "bracketSameLine": true, "arrowParens": "always", - "endOfLine": "lf" + "endOfLine": "lf", + "quoteProps": "as-needed" } diff --git a/src/components/back_to_top/index.js b/src/components/back_to_top/index.js index c430590a..28cb244f 100644 --- a/src/components/back_to_top/index.js +++ b/src/components/back_to_top/index.js @@ -1,20 +1,20 @@ /** * @file index.js - * @description Example Component entry file (index.js). - * @module example + * @description BackToTop Component entry file (index.js). + * @module BackToTop */ // Imports QGDS Component utility: import QGDSComponent from "./../../js/QGDSComponent.js"; -// Imports resources needed to nake our "Example" component: +// Imports resources needed to make our "BackToTop" component: import hbstemplate from "./back-to-top.hbs?raw"; import logic from "./back-to-top.js"; import meta from "./version.json"; /** * @function BackToTop - * @description The Example component. + * @description The BackToTop component. * @param {object} data - The data to be used in the template. * @param {string} template - The template to render. * @returns {object} - A new instance of the QGDSComponent class, contained properties: template, meta, htmlstring, node. diff --git a/src/components/in_page_navigation/stories/InPageNavigation.mdx b/src/components/in_page_navigation/_other/_InPageNAvigation.mdx similarity index 100% rename from src/components/in_page_navigation/stories/InPageNavigation.mdx rename to src/components/in_page_navigation/_other/_InPageNAvigation.mdx diff --git a/src/components/in_page_navigation/html/example1.test.hbs b/src/components/in_page_navigation/_other/example1.test.hbs similarity index 100% rename from src/components/in_page_navigation/html/example1.test.hbs rename to src/components/in_page_navigation/_other/example1.test.hbs diff --git a/src/components/in_page_navigation/html/component.hbs b/src/components/in_page_navigation/html/component.hbs deleted file mode 100644 index b216a3bc..00000000 --- a/src/components/in_page_navigation/html/component.hbs +++ /dev/null @@ -1,17 +0,0 @@ -{{!-- {{#ifCond current.data.metadata.pageType.value '==' 'landing'}} --}} -
-
-{{!-- {{/ifCond}} --}} - - - -{{!-- {{#ifCond current.data.metadata.pageType.value '==' 'landing'}} --}} -
-
-{{!-- {{/ifCond}} --}} - diff --git a/src/components/in_page_navigation/html/example1.json b/src/components/in_page_navigation/html/example1.json deleted file mode 100644 index 1f7da8f0..00000000 --- a/src/components/in_page_navigation/html/example1.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "component": { - "name": "In Page Navigation", - "description": "", - "version": "1.0", - "status": "Released", - "data": { - "metadata": { - "heading": { - "type": "metadata_field_text", - "description": "", - "friendly_name": "Navigation Heading", - "value": "On This Page", - "required": false, - "editable": true - }, - "headingType": { - "type": "metadata_field_select", - "description": "", - "friendly_name": "Heading Type", - "value": "h2", - "options": { - "h2": "H2", - "h3": "H3", - "h4": "H4", - "h5": "H5", - "h6": "H6" - }, - "required": false, - "editable": true - } - } - } - } -} \ No newline at end of file diff --git a/src/components/in_page_navigation/index.js b/src/components/in_page_navigation/index.js new file mode 100644 index 00000000..486e113b --- /dev/null +++ b/src/components/in_page_navigation/index.js @@ -0,0 +1,49 @@ +/** + * @file index.js + * @module InpageNavigation + * @description Inpage Navigation Component file. + * + * @function InpageNavigation + * @description The Inpage Navigation component. + * @param {object} data - The data to be used by the template. + * @param {string} template - The handlebars template to render. + * @returns {object} - A new instance of the QGDSComponent class, containing properties: template, meta, htmlstring, node. + */ + +// Imports QGDS Component utility: +import QGDSComponent from "./../../js/QGDSComponent.js"; + +// Imports resources needed to make our "Inpage Navigation" component: +import hbstemplate from "./inpage-navigation.hbs?raw"; +import logic from "./inpage-navigation.js"; +import meta from "./version.json"; + +export default function InpageNavigation({ data, template = hbstemplate }) { + // Initialise Inpage Navigation JS on page load (if data.dynamiclinks is true) + document.addEventListener("DOMContentLoaded", () => { + try { + if (data.source === "dynamic") { + /** + * Initialize the logic for Inpage Navigation. + * @function init + * @memberof logic + */ + logic.init(); + } + } catch (error) { + console.error(`InpageNavigation error in function 'init': ${error.message}`, { + functionName: "init", + errorDetails: error, + }); + } + }); + + //Minimum required fields for the component to function + const props = { + data: data, + template: template, + meta: meta || {}, + }; + + return new QGDSComponent("InpageNavigation", props); +} diff --git a/src/components/in_page_navigation/inpage-navigation.data.json b/src/components/in_page_navigation/inpage-navigation.data.json new file mode 100644 index 00000000..67cfcf70 --- /dev/null +++ b/src/components/in_page_navigation/inpage-navigation.data.json @@ -0,0 +1,25 @@ +{ + "id": "in-page-navigation", + "component": "inpage-navigation", + "title": "On this page", + "headingType": "h2", + "source": "static", + "links": [ + { + "href": "#section-1", + "title": "Static Link Section 1" + }, + { + "href": "#section-2", + "title": "Static Link Section 2" + }, + { + "href": "#section-3", + "title": "Static Link Section 3" + }, + { + "href": "#section-4", + "title": "Static Link Section 4" + } + ] +} diff --git a/src/components/in_page_navigation/inpage-navigation.hbs b/src/components/in_page_navigation/inpage-navigation.hbs new file mode 100644 index 00000000..28243d3b --- /dev/null +++ b/src/components/in_page_navigation/inpage-navigation.hbs @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/src/components/in_page_navigation/inpage-navigation.js b/src/components/in_page_navigation/inpage-navigation.js new file mode 100644 index 00000000..5c19e503 --- /dev/null +++ b/src/components/in_page_navigation/inpage-navigation.js @@ -0,0 +1,36 @@ +export default { + init() { + var navs = document.querySelectorAll(".qld__inpage-nav-links"); + var mainEl = document.querySelector("main.main"); + var isLandingPage = mainEl && mainEl.classList.contains("landing"); + + // For all In-Page Nav components + navs.forEach(function (nav) { + var headingSelector = nav.getAttribute("data-headingType") ? nav.getAttribute("data-headingType") : "h2"; + var pageContent = isLandingPage ? mainEl : document.getElementById("content"); + var headings = pageContent.querySelectorAll( + headingSelector + ":not(.qld__inpage-nav-links__heading):not(.banner__heading)", + ); + var list = nav.querySelector(".qld__link-list"); + list.innerHTML = ""; + + if (headings.length === 0) { + nav.style.display = "none"; + } + + // For all headings (with matching data-headingType) in page content + headings.forEach(function (heading) { + var title = heading.innerText; + var id = "section__" + title.toLowerCase().replace(/\s+/g, "-"); + heading.setAttribute("id", id); + heading.setAttribute("tabindex", -1); + var link = '
  • ' + title + "
  • "; + + // Append link item if it doesn't already exist in list + if (list.querySelector('a[href="#' + id + '"') === null) { + list.insertAdjacentHTML("beforeend", link); + } + }); + }); + }, +}; diff --git a/src/components/in_page_navigation/css/component.scss b/src/components/in_page_navigation/inpage-navigation.scss similarity index 87% rename from src/components/in_page_navigation/css/component.scss rename to src/components/in_page_navigation/inpage-navigation.scss index bf14df9a..d3047725 100644 --- a/src/components/in_page_navigation/css/component.scss +++ b/src/components/in_page_navigation/inpage-navigation.scss @@ -18,16 +18,17 @@ line-height: $typographyDesktopH6LineHeight; } - * + & { + *+& { @include QLD-space(margin-top, 2.3125unit); } + a { @include QLD-underline("light", "underline", "default", "noVisited"); } - .qld__body & > ul, - .qld__body & > ol { - @include QLD-space(margin-top, 0.75unit); + .qld__body &>ul, + .qld__body &>ol { + @include QLD-space(margin-top, 0.75unit); // Consider using a variable for 0.75unit list-style-type: none; padding: 0; @@ -35,17 +36,19 @@ margin: 0; } - * + li { + *+li { margin-left: 0; } - li + li { - @include QLD-space(margin-top, 0.5unit); + + li+li { + @include QLD-space(margin-top, 0.5unit); // Consider using a variable for 0.5unit } } .qld__body--dark &, .qld__body--dark-alt & { border-color: var(--QLD-color-dark__action--primary); + a { @include QLD-underline("dark", "underline", "default", "noVisited"); } @@ -74,4 +77,4 @@ .qld__inpage-nav-links { display: none !important; } -} +} \ No newline at end of file diff --git a/src/components/in_page_navigation/inpage-navigation.stories.js b/src/components/in_page_navigation/inpage-navigation.stories.js new file mode 100644 index 00000000..ab43346e --- /dev/null +++ b/src/components/in_page_navigation/inpage-navigation.stories.js @@ -0,0 +1,120 @@ +/** + * @file inpage-navigation.stories.js + * @description Storybook configuration file for the Inpage Navigation component. + * @module inpage-navigation.stories + */ + +// Imports: +// - the QGDS object containing all components +// - data you need to populate the component for rendering +import { QGDS } from "../../js/index.js"; +import mockupData from "./inpage-navigation.data.json"; + +/* ========= STORIES 👇 ===== */ + +export default { + title: "Components/Navigation (In-page navigation)", + render: (args) => { + try { + return new QGDS.InpageNavigation({ data: args }).htmlstring; + } catch (e) { + return JSON.stringify(e) + JSON.stringify(args); + } + }, + args: mockupData, + + decorators: [ + (Story) => { + return ` +
    +
    + ${Story()} +
    +
    `; + }, + ], + + /** + * Additional parameters for the story. + * + * @type {Object} + * @property {Object} design - Configuration for the design parameter. + * @property {string} design.name - Name of the design parameter. + * @property {string} design.type - Type of the design parameter. figma | link + * @property {string} design.url - URL of the design parameter. + */ + parameters: { + design: [ + { + name: "Link", + type: "link", + url: "https://www.figma.com/design/qKsxl3ogIlBp7dafgxXuCA/QLD-GOV-DDS?node-id=7229-112138", + }, + { + name: "QGDS Figma Reference", + type: "figma", + url: "https://www.figma.com/design/qKsxl3ogIlBp7dafgxXuCA/QLD-GOV-DDS?node-id=7229-112138", + }, + { + name: "dark xl", + type: "figma", + url: "https://www.figma.com/design/qKsxl3ogIlBp7dafgxXuCA/QLD-GOV-DDS?node-id=10865-242473", + }, + ], + }, +}; + +/** + * In-page Navigation with Static links (Default) + */ + +export const WithStaticLinks = { + args: { + ...mockupData, + source: "static", + }, +}; + +/** + * In-page Navigation with dynamic links + */ + +export const WithDynamicLinks = { + args: { + ...mockupData, + source: "dynamic", + }, + decorators: [ + (Story) => { + return ` +
    + +

    Page title

    + + + ${Story()} + +

    Section 1

    +

    + This inpage navigation list was generated with Javascript that looped over all H2 tags within the #content container. +

    + +

    Section 2

    +

    This is a paragraph under section 2. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc ultricies ultricies.

    + +

    Sub-section

    +

    This is a sub-section under section 2.

    + +

    Sub-section

    +

    This is a sub-section under section 2.

    + +

    Section 3

    +

    + This is a paragraph under section 3. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Nullam nec purus nec nunc ultricies ultricies. Nullam nec purus nec nunc ultricies ultricies. +

    + +
    `; + }, + ], +}; diff --git a/src/components/in_page_navigation/inpage-navigation.test.js b/src/components/in_page_navigation/inpage-navigation.test.js new file mode 100644 index 00000000..437ffa7f --- /dev/null +++ b/src/components/in_page_navigation/inpage-navigation.test.js @@ -0,0 +1,21 @@ +/** + * @jest-environment jsdom + */ + +import { expect, test } from "vitest"; + +import InpageNavigation from "./index.js"; +import MockData from "./inpage-navigation.data.json"; + +test("Inpage Navigation initiated", () => { + const InpageNavigationComponent = new InpageNavigation({ data: MockData }); + expect(InpageNavigationComponent).toBeDefined(); +}); + +//1. Test that the component renders the correct HTML structure: +test("Inpage Navigation renders the correct structure", () => { + const InpageNavigationComponent = new InpageNavigation({ data: MockData }); + expect(InpageNavigationComponent.htmlstring).toContain(""); + expect(InpageNavigationComponent.htmlstring).toMatch(/