diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 353e96c1..d899b204 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,205 +7,11 @@ on: pull_request: branches: - '1.x' - -env: - LOCALGOV_DRUPAL_PROJECT: localgovdrupal/localgov_base - LOCALGOV_DRUPAL_PROJECT_PATH: web/themes/contrib/localgov_base + workflow_dispatch: jobs: - - build: - name: Install LocalGov Drupal - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Save git branch and git repo names to env if this is not a pull request - if: github.event_name != 'pull_request' - run: | - echo "GIT_BASE=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - echo "GIT_BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - echo "HEAD_USER=localgovdrupal" >> $GITHUB_ENV - - - name: Save git branch and git repo names to env if this is a pull request - if: github.event_name == 'pull_request' - run: | - echo "GIT_BASE=${GITHUB_BASE_REF}" >> $GITHUB_ENV - echo "GIT_BRANCH=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - export HEAD="${{ github.event.pull_request.head.label }}" - echo "HEAD_USER=${HEAD%%:*}" >> $GITHUB_ENV - - - name: Set composer branch reference for version branches - if: endsWith(github.ref, '.x') - run: echo "COMPOSER_REF=${GIT_BRANCH}-dev" >> $GITHUB_ENV - - - name: Set composer branch reference for non-version branches - if: endsWith(github.ref, '.x') == false - run: echo "COMPOSER_REF=dev-${GIT_BRANCH}" >> $GITHUB_ENV - - - name: Get the latest tagged release for branch version - run: | - LATEST_RELEASE=$(curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${GITHUB_REPOSITORY}/git/matching-refs/tags/${GIT_BASE%'.x'} | grep -Po '(?<=refs/tags/)[^"]+' | tail -1) - if [ -z $LATEST_RELEASE ]; then LATEST_RELEASE=1; fi - echo "LATEST_RELEASE=${LATEST_RELEASE}" >> $GITHUB_ENV - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - - name: Clone drupal_container - uses: actions/checkout@v2 - with: - repository: localgovdrupal/drupal-container - ref: php${{ matrix.php-version }} - - - name: Create LocalGov Drupal project - run: | - composer create-project --stability dev --no-install localgovdrupal/localgov-project ./html "${{ matrix.localgov-version }}" - composer --working-dir=./html require --no-install localgovdrupal/localgov:${{ matrix.localgov-version }}-dev - composer --working-dir=./html require --no-install drupal/core-recommended:${{ matrix.drupal-version }} drupal/core-composer-scaffold:${{ matrix.drupal-version }} drupal/core-project-message:${{ matrix.drupal-version }} drupal/core-dev:${{ matrix.drupal-version }} - composer --working-dir=./html install - - - name: Obtain the test target using Composer - if: env.HEAD_USER == 'localgovdrupal' - run: | - composer --working-dir=html config repositories.1 vcs git@github.com:${LOCALGOV_DRUPAL_PROJECT}.git - composer global config github-oauth.github.com ${{ github.token }} - composer --working-dir=./html require --with-all-dependencies ${LOCALGOV_DRUPAL_PROJECT}:"${COMPOSER_REF} as ${LATEST_RELEASE}" - - - name: Obtain the test target using Git - if: env.HEAD_USER != 'localgovdrupal' - uses: actions/checkout@v2 - with: - path: ${{ env.LOCALGOV_DRUPAL_PROJECT_PATH }} - - phpcs: - name: Coding standards checks - needs: build - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - restore-keys: | - localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}- - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - - name: Run coding standards checks - run: | - cd html - ./bin/phpcs -p ${LOCALGOV_DRUPAL_PROJECT_PATH} - - phpstan: - name: Deprecated code checks - needs: build - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - restore-keys: | - localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}- - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - - name: Run deprecated code checks - run: | - cd html - ./bin/phpstan analyse -c ./phpstan.neon ${LOCALGOV_DRUPAL_PROJECT_PATH} - phpunit: - name: PHPUnit tests - needs: build - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Clone Drupal container - uses: actions/checkout@v2 - with: - repository: localgovdrupal/drupal-container - ref: php${{ matrix.php-version }} - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - restore-keys: | - localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}- - - - name: Start Docker environment - run: docker-compose -f docker-compose.yml up -d - - - name: Run PHPUnit tests - run: | - mkdir -p ./html/web/sites/simpletest && chmod 777 ./html/web/sites/simpletest - sed -i "s#http://localgov.lndo.site#http://drupal#" ./html/phpunit.xml.dist - docker exec -t drupal bash -c 'chown docker:docker -R /var/www/html' - docker exec -u docker -t drupal bash -c "cd /var/www/html && ./bin/paratest --processes=4 /var/www/html/${{ env.LOCALGOV_DRUPAL_PROJECT_PATH }}" + tests: + uses: localgovdrupal/localgov_shared_workflows/.github/workflows/test-module.yml@1.x + with: + project: 'localgovdrupal/localgov_base' + project_path: 'web/themes/contrib/localgov_base' diff --git a/config/install/localgov_base.settings.yml b/config/install/localgov_base.settings.yml index 5e4015f4..7e8914b3 100644 --- a/config/install/localgov_base.settings.yml +++ b/config/install/localgov_base.settings.yml @@ -2,3 +2,4 @@ localgov_base_remove_css: FALSE localgov_base_remove_js: FALSE localgov_base_add_unpublished_background_colour: TRUE localgov_base_add_draft_note_to_unpublished_content: FALSE +localgov_base_header_behaviour: 'default' diff --git a/config/schema/localgov_base.schema.yml b/config/schema/localgov_base.schema.yml index c6148cc3..5dccf790 100644 --- a/config/schema/localgov_base.schema.yml +++ b/config/schema/localgov_base.schema.yml @@ -14,3 +14,9 @@ localgov_base.settings: localgov_base_add_draft_note_to_unpublished_content: type: boolean label: 'Add "[Draft]" to title of unpublished content.' + localgov_base_header_behaviour: + type: string + label: 'Header behaviour' + description: 'Choose whether the header should be sticky or not.' + constraints: + Regex: '^(default|sticky|appears_on_scroll)$' diff --git a/css/base/base.css b/css/base/base.css index b651e2b1..ca98ed79 100644 --- a/css/base/base.css +++ b/css/base/base.css @@ -16,6 +16,10 @@ html { margin-top: 0; /* Removing top margin, for better vertical rhythm layout */ } +dialog { + margin: auto; +} + body { margin: 0; color: var(--color-text); @@ -158,6 +162,13 @@ ol ul { margin-bottom: 0; } +blockquote:not(.pull-out-quote__content) { + padding-inline-start: var(--quote-padding); + padding-left: var(--quote-padding-left); + border-inline-start: var(--quote-border); + border-color: var(--color-accent); +} + sub, sup { position: relative; diff --git a/css/base/ckeditor5.css b/css/base/ckeditor5.css index 8fb8a07a..f6662058 100644 --- a/css/base/ckeditor5.css +++ b/css/base/ckeditor5.css @@ -52,7 +52,7 @@ position: relative; top: 7px; margin-left: 0.5rem; - content: "\203A"; + content: "\203A" / ""; font-size: 2.875rem; } diff --git a/css/base/variables.css b/css/base/variables.css index db52962d..c0660ca7 100644 --- a/css/base/variables.css +++ b/css/base/variables.css @@ -141,6 +141,9 @@ body { /* Animation */ --transition-time: 0.3s; + + /* Dialog */ + --dialog-backdrop-color: rgba(0, 0, 0, 0.8); } /* @@ -540,4 +543,8 @@ body { --add-to-calendar-dialog-backdrop-opacity: 0.75; --add-to-calendar--icon-color: var(--color-white); --add-to-calendar--icon-color-hover: var(--color-accent); + + /* Accordion */ + --accordion-icon-closed: "+"; + --accordion-icon-opened: "-"; } diff --git a/css/components/accordion.css b/css/components/accordion.css new file mode 100644 index 00000000..4ccfa498 --- /dev/null +++ b/css/components/accordion.css @@ -0,0 +1,33 @@ +/** + * @file + * Style rules for accordions. + */ + +/* Default */ +.accordion-pane__title button { + display: inline-flex; + align-items: center; + justify-content: space-between; +} + +.accordion-pane__title button:hover, +.accordion-pane__title button:focus { + text-decoration: none; +} + +.accordion-icon { + display: block; +} + +span.accordion-icon::after { + display: block; + font-size: 150%; +} + +.accordion-pane__title button[aria-expanded="false"] > .accordion-icon::after { + content: var(--accordion-icon-closed); +} + +.accordion-pane__title button[aria-expanded="true"] > .accordion-icon::after { + content: var(--accordion-icon-opened); +} diff --git a/css/components/alert-banner.css b/css/components/alert-banner.css index 191dc4f5..abb69446 100644 --- a/css/components/alert-banner.css +++ b/css/components/alert-banner.css @@ -8,6 +8,14 @@ background-color: var(--alert-banner-bg-color); } +dialog.localgov-alert-banner { + margin: auto; +} + +dialog.localgov-alert-banner::backdrop { + background-color: var(--dialog-backdrop-color); +} + .localgov-alert-banner, .localgov-alert-banner a { color: var(--alert-banner-color); diff --git a/css/components/back-to-top.css b/css/components/back-to-top.css new file mode 100644 index 00000000..96f6ee8f --- /dev/null +++ b/css/components/back-to-top.css @@ -0,0 +1,41 @@ +.back-to-top { + background: var(--button-link-bg-color); + border: 3px solid var(--color-white); + bottom: 2em; + color: var(--button-link-color); + display: flex; + font-weight: bold; + gap: var(--spacing-small); + padding: var(--button-link-padding); + position: fixed; + right: 2em; + text-decoration: none; + transition: var(--transition-time); + opacity: 1; +} + +.back-to-top[hidden] { + opacity: 0; +} + +.back-to-top:hover, +.back-to-top:focus { + text-decoration: underline; +} + +.back-to-top__icon svg path { + fill: currentcolor; +} + +.back-to-top-target { + max-height: 1px; + max-width: 1px; + overflow: hidden; +} + +@media screen and (min-width: 48rem) { + .back-to-top { + bottom: 5em; + right: 5em; + } +} diff --git a/css/components/form-items.css b/css/components/form-items.css index 05af79bf..c28e313e 100644 --- a/css/components/form-items.css +++ b/css/components/form-items.css @@ -360,3 +360,25 @@ input[type="file"]:hover, .form-item-managed-file-button .description br { margin-bottom: var(--spacing); } + +.facets-form fieldset { + padding: 0; + border: none; +} + +.facets-form .facets-widget > ul, +.facets-form .facets-widget > ul ul { + list-style: none; + padding: 0; +} + +.facets-form .facets-widget > ul > li + li { + margin-block-start: var(--spacing-largest); +} + +.facets-form .form-actions { + display: flex; + flex-wrap: wrap; + gap: var(--spacing); + align-items: center; +} diff --git a/css/components/guide-nav.css b/css/components/guide-nav.css index c1023dfa..6e1acefb 100644 --- a/css/components/guide-nav.css +++ b/css/components/guide-nav.css @@ -6,6 +6,7 @@ .lgd-guide-nav__list { margin-bottom: 0; + word-wrap: break-word; } @media screen and (min-width: 48rem) { diff --git a/css/components/header.css b/css/components/header.css index a495be8c..028159a2 100644 --- a/css/components/header.css +++ b/css/components/header.css @@ -104,7 +104,7 @@ .lgd-header__toggle-icon::after { display: inline-block; margin-left: var(--spacing-smaller); - content: "\203A"; + content: "\203A" / ""; transition: var(--transition-time); transform: rotate(90deg); font-size: var(--font-size-larger); diff --git a/css/components/pager.css b/css/components/pager.css index 45b20f91..188148bb 100644 --- a/css/components/pager.css +++ b/css/components/pager.css @@ -14,3 +14,7 @@ .pager__item::marker { color: transparent; } + +.pager__item > a { + padding-inline: var(--spacing-smaller); +} diff --git a/css/components/quote.css b/css/components/quote.css index d3babca1..28d63c67 100644 --- a/css/components/quote.css +++ b/css/components/quote.css @@ -5,7 +5,7 @@ padding: var(--quote-padding); padding-left: var(--quote-padding-left); border-color: var(--quote-border-color); - border-left: var(--quote-border); + border-inline-start: var(--quote-border); background-color: var(--quote-bg-color); } diff --git a/css/components/service-statuses.css b/css/components/service-statuses.css index 1b9332d1..829bf06c 100644 --- a/css/components/service-statuses.css +++ b/css/components/service-statuses.css @@ -18,6 +18,7 @@ .service-statuses__label { margin-bottom: 0; + color: var(--service-statuses-container-text-color); } .service-statuses__link { diff --git a/css/components/sticky-header.css b/css/components/sticky-header.css new file mode 100644 index 00000000..d45d3692 --- /dev/null +++ b/css/components/sticky-header.css @@ -0,0 +1,15 @@ +.sticky-header--sticky .lgd-header { + top: var(--lgd-sticky-header-position); + width: 100%; + z-index: 1000; +} + +.sticky-header--sticky .lgd-header + * { + margin-top: calc(var(--lgd-sticky-header-position) + var(--lgd-sticky-header-height)); + scroll-padding: var(--lgd-sticky-header-height); +} + +.sticky-header-html, +.sticky-header--sticky { + scroll-padding-top: var(--lgd-sticky-header-height); +} diff --git a/css/components/wysiwyg-styles.css b/css/components/wysiwyg-styles.css index 4176603e..86ef7920 100644 --- a/css/components/wysiwyg-styles.css +++ b/css/components/wysiwyg-styles.css @@ -53,7 +53,7 @@ position: relative; top: var(--btn-start-icon-top); margin-left: 0.5rem; - content: var(--btn-start-icon); + content: var(--btn-start-icon) / ""; font-size: var(--btn-start-icon-size); line-height: 0; } diff --git a/css/print/print.css b/css/print/print.css index 31fbabaf..3ee26b2c 100644 --- a/css/print/print.css +++ b/css/print/print.css @@ -3,7 +3,10 @@ .lgd-region__inner--header > *:not(.block-system-branding-block), .sidebar, .lgd-prev-next, -.newsroom__sidebar { +.newsroom__sidebar, +.block-localgov-guides-contents, +.block-views-blocklocalgov-step-by-step-navigation-steps-for-overview, +.block-views-blocklocalgov-step-by-step-navigation-steps { display: none; } diff --git a/js/back-to-top.js b/js/back-to-top.js new file mode 100644 index 00000000..4dc4e910 --- /dev/null +++ b/js/back-to-top.js @@ -0,0 +1,65 @@ +/** + * @file Drupal behavior for 'back to top' link. + */ + +(function (Drupal) { + Drupal.behaviors.localgovBackToTop = { + attach(context) { + /** + * Callback for intersection observer. + * + * Sets back to top link's hidden attribute to 'false' if the intersection + * target is either: + * + * - intersecting the viewport, OR + * - has been scrolled UP out of the viewport + * + * @param {IntersectionObserverEntry[]} entries + * The qualifying entries to be checked for intersection with, or + * past the top of the viewport. In practices, there's only one of + * these since we've only targeted one element. + */ + function observerCallback(entries) { + entries.forEach((entry) => { + backToTop.hidden = ( + entry.isIntersecting || + (!entry.isIntersecting && entry.boundingClientRect.top <= 0) + ) ? false : 'until-hidden'; + }); + } + + const [backToTop] = once('back-to-top', '.back-to-top', context); + const [backToTopTarget] = once( + 'back-to-top-target', + '.back-to-top-target', + context, + ); + const minContentViewportRatio = parseFloat( + backToTop?.dataset?.minContentViewportRatio ?? 1.5, + 10, + ); + const viewportHeight = window.innerHeight; + const documentHeight = document.documentElement.offsetHeight; + let intersectionObserver; + + if ( + !backToTop || + !backToTopTarget || + documentHeight / viewportHeight < minContentViewportRatio + ) { + return; + } + + // Create an element absolutely positioned at our threshold. + backToTopTarget.style.position = 'absolute'; + backToTopTarget.style.top = `${viewportHeight * minContentViewportRatio}px`; + backToTop.addEventListener('click', (event) => event.target.hidden = 'until-found'); + + // Create an IntersectionObserver. + intersectionObserver = new IntersectionObserver(observerCallback, { + rootMargin: '16px', + }); + intersectionObserver.observe(backToTopTarget); + }, + }; +})(Drupal); diff --git a/js/header.js b/js/header.js index e73ca44f..01638439 100644 --- a/js/header.js +++ b/js/header.js @@ -47,8 +47,11 @@ // The resulting region.primary.firstLink isn't used, but it's less // difficult to add it than to add only region.secondary.firstLink. if (region) { - const firstLink = region.querySelector('.menu a'); - navInfo[nav] = { toggle, region, firstLink }; + const links = region.querySelectorAll('.menu a'); + const firstLink = links[0]; + const lastLink = links[links.length - 1]; + + navInfo[nav] = { toggle, region, firstLink, lastLink }; } }); @@ -101,12 +104,23 @@ navInfo.secondary.firstLink.addEventListener('keydown', function(e) { if (e.shiftKey && e.key == 'Tab') { e.preventDefault(); - handleReset(); navInfo.secondary.toggle.focus(); } }); } + // When on the last link in the secondary menu, if you hit tab + // set focus back to the services button + function handleSecondaryMenuTabClick() { + navInfo.secondary.lastLink.addEventListener('keydown', function(e) { + if (e.key == 'Tab') { + e.preventDefault(); + navInfo.secondary.toggle.focus(); + } + }); + } + + // General function for when the ESC is clicked. function handleEscKeyClick(buttonToFocus) { context.addEventListener('keydown', function(e) { @@ -143,6 +157,7 @@ if (Object.keys(navInfo).includes('secondary') && navInfo.secondary.toggle) { navInfo.secondary.toggle.removeEventListener('click', handleSecondaryMenuToggleClick, true); navInfo.secondary.toggle.removeEventListener('click', handleSecondaryMenuShiftTabClick, true); + navInfo.secondary.toggle.removeEventListener('click', handleSecondaryMenuTabClick, true); } if (navInfo.primary.toggle) { navInfo.primary.toggle.addEventListener('click', handlePrimaryMenuToggleClick); @@ -154,6 +169,7 @@ if (Object.keys(navInfo).includes('secondary') && navInfo.secondary.toggle) { navInfo.secondary.toggle.addEventListener('click', handleSecondaryMenuToggleClick); navInfo.secondary.toggle.addEventListener('click', handleSecondaryMenuShiftTabClick); + navInfo.secondary.toggle.addEventListener('keyup', handleSecondaryMenuTabClick); } } } diff --git a/js/sticky-header.js b/js/sticky-header.js new file mode 100644 index 00000000..9ae05dba --- /dev/null +++ b/js/sticky-header.js @@ -0,0 +1,65 @@ +(function stickyHeaderScript(Drupal) { + Drupal.behaviors.stickyHeader = { + attach: function (context) { + const headers = once('allSticyHeaders', '.lgd-header', context); + + if (!headers) { + return; + } + + headers.forEach(header => { + function calculatePositions() { + let tabsHeight = 0; + const tabs = header.closest('body').querySelector('.lgd-region--tabs'); + if (tabs) { + tabsHeight = tabs.offsetHeight; + } + + let displaceOffsetTop = 0; + const displaceOffsetTopValue = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--drupal-displace-offset-top').replace('px', '')); + if (displaceOffsetTopValue) { + displaceOffsetTop = displaceOffsetTopValue; + } + + const headerHeight = header.offsetHeight; + const headerPosition = displaceOffsetTop + tabsHeight; + + if (header.closest('body').classList.contains('sticky-header')) { + document.documentElement.style.setProperty('--lgd-sticky-header-position', `${headerPosition}px`); + document.documentElement.style.setProperty('--lgd-sticky-header-height', `${headerHeight}px`); + } + + if (header.closest('body').classList.contains('sticky-header--sticky')) { + header.style.position = 'fixed'; + } + } + + // Initialize oldScroll, so we can use it in the scroll event. + let oldScroll = window.scrollY; + + function handleScroll() { + if (oldScroll > window.scrollY) { + header.closest('body').classList.add('sticky-header--sticky'); + } else { + header.closest('body').classList.remove('sticky-header--sticky'); + header.style.position = 'relative'; + } + // Update oldScroll to the new scroll position after the comparison + oldScroll = window.scrollY; + + calculatePositions(); + } + + if (header.closest('body').classList.contains('sticky-header--scroll')) { + window.addEventListener('scroll', handleScroll); + } + + setTimeout(() => { + calculatePositions(); + }, 50); + + }); + + } + }; +})(Drupal); \ No newline at end of file diff --git a/localgov_base.info.yml b/localgov_base.info.yml index eca7def8..078ff519 100644 --- a/localgov_base.info.yml +++ b/localgov_base.info.yml @@ -36,3 +36,5 @@ regions: libraries-extend: localgov_eu_cookie_compliance/localgov_eu_cookie_compliance: - localgov_base/localgov_eu_cookie_compliance + localgov_subsites_paragraphs/localgov_accordion: + - localgov_base/accordion diff --git a/localgov_base.libraries.yml b/localgov_base.libraries.yml index 3ce9f3be..27f388d5 100644 --- a/localgov_base.libraries.yml +++ b/localgov_base.libraries.yml @@ -49,15 +49,14 @@ wysiwyg-styles: theme: css/components/wysiwyg-styles.css: {} -header: - css: - theme: - css/components/header.css: {} - secondary-menu: css: theme: css/components/secondary-menu.css: {} +header: + css: + theme: + css/components/header.css: {} header-js: js: @@ -67,6 +66,17 @@ header-js: - core/drupal.debounce - core/once +sticky-header: + css: + theme: + css/components/sticky-header.css: {} + js: + js/sticky-header.js: {} + dependencies: + - core/drupal + - core/drupal.debounce + - core/once + footer: css: theme: @@ -317,3 +327,18 @@ add-to-calendar: dependencies: - core/drupal - core/once + +back-to-top: + css: + theme: + css/components/back-to-top.css: {} + js: + js/back-to-top.js: {} + dependencies: + - core/drupal + - core/once + +accordion: + css: + theme: + css/components/accordion.css: {} diff --git a/localgov_base.theme b/localgov_base.theme index 57ca9523..f5106ce7 100644 --- a/localgov_base.theme +++ b/localgov_base.theme @@ -46,6 +46,52 @@ function localgov_base_form_system_theme_settings_alter(&$form, FormStateInterfa '#default_value' => theme_get_setting('localgov_base_add_draft_note_to_unpublished_content'), '#description' => t('This adds the word "Draft" to the title of any unpublished content. This is useful if you have removed the pink background from unpublished content.'), ]; + $form['localgov_base_show_back_to_top_link'] = [ + '#type' => 'checkbox', + '#title' => t('Display a "Back to Top" link on long pages.'), + '#default_value' => theme_get_setting('localgov_base_show_back_to_top_link'), + '#description' => t('This adds a link to the beginning of the page content (#main-content).'), + ]; + $form['localgov_base_header_behaviour'] = [ + '#type' => 'radios', + '#title' => t('Header behaviour'), + '#default_value' => theme_get_setting('localgov_base_header_behaviour'), + '#options' => [ + 'default' => t('Default - scrolls away with the page'), + 'sticky' => t('Sticky - remains at the top of the page'), + 'appears_on_scroll' => t('Scroll - appears when scrolling up the page'), + ], + '#description' => t('Select how you want the header to behave. This setting only apply to anonymous users.'), + ]; + +} + +/** + * Implements hook_preprocess_html(). + */ +function localgov_base_preprocess_html(&$variables) { + // Add the 'sticky-header' library if the sticky header setting is enabled. + if (theme_get_setting('localgov_base_header_behaviour') !== 'default') { + + // Check if the user is logged out. + // We have much fewer calculations if we keep this to anonymous users only. + if (\Drupal::currentUser()->isAnonymous()) { + $variables['#attached']['library'][] = 'localgov_base/sticky-header'; + $variables['attributes']['class'][] = 'sticky-header'; + $variables['html_attributes']['class'] = []; + $variables['html_attributes']['class'][] = 'sticky-header-html'; + + // If 'sticky' is chosen add a class to the body element. + if (theme_get_setting('localgov_base_header_behaviour') === 'sticky') { + $variables['attributes']['class'][] = 'sticky-header--sticky'; + } + + // If 'scroll' is chosen add a class to the body element. + if (theme_get_setting('localgov_base_header_behaviour') === 'appears_on_scroll') { + $variables['attributes']['class'][] = 'sticky-header--scroll'; + } + } + } } /** @@ -94,6 +140,11 @@ function localgov_base_preprocess_page(&$variables) { if (theme_get_setting('localgov_base_add_unpublished_background_colour') === TRUE) { $variables['#attached']['library'][] = 'localgov_base/unpublished-bg'; } + + // Render the Back to Top link or not according to the theme setting. + if (theme_get_setting('localgov_base_show_back_to_top_link')) { + $variables['back_to_top'] = TRUE; + } } /** diff --git a/templates/_components/back-to-top.twig b/templates/_components/back-to-top.twig new file mode 100644 index 00000000..d0b4f586 --- /dev/null +++ b/templates/_components/back-to-top.twig @@ -0,0 +1,51 @@ +{# +/** + * Template for "back-to-top" link. + * + * The template includes two HTML elements: + * + * 1. the "back-to-top" link itself + * 2. a "back-to-top-target" span + * + * The javascript from localgov_base/back-to-top interacts with this template + * as follows: + * + * - absolutely positions the target span + * - creates an intersection observer to observe it + * - un-hides the link when the target enters the viewport + * - re-hides the link when the target exits the *bottom* of the viewport + * - re-hides the link when it's clicked [1] + * + * The template includes no wrapping
element, and the css explicitly sets + * the maximum size of the target to 1px x 1px to minimize the chances of an + * element from the template blocking content. + * + * [1] on small enough screens--especially with several visible banners--the + * link will *already* be visible when #main-content is reached, so re-hiding + * the link fails in that circumstance. + */ +#} + +{% set default_ratio = 1.5 %} +{% set btt_icon_name = 'arrow-up' %} +{% set btt_icon_class = 'back-to-top__icon' %} +{% set btt_link_text = 'Back to top' %} +{% set btt_link_class = 'back-to-top' %} +{% set btt_target_class = 'back-to-top-target' %} + +{{ attach_library('localgov_base/back-to-top') }} + + + diff --git a/templates/_components/prev-next.twig b/templates/_components/prev-next.twig index 8cfe5d2d..3f3f90a4 100644 --- a/templates/_components/prev-next.twig +++ b/templates/_components/prev-next.twig @@ -7,6 +7,8 @@ {% set previous_icon = previous_icon|default('chevron-left') %} {% set next_icon = next_icon|default('chevron-right') %} +{% set previous_aria_label = 'Previous'|t ~ (previous_title ? ': ' ~ previous_title : '') %} +{% set next_aria_label = 'Next'|t ~ (next_title ? ': ' ~ next_title : '') %} {% set classes = [ @@ -23,7 +25,7 @@
{% endif %} @@ -224,7 +222,7 @@ {% endif %} {# End Contact Socials and a11y number #} - + {% endif %} {# End Contact Section #} diff --git a/templates/layout/header.html.twig b/templates/layout/header.html.twig index 5cb96a20..63608dc4 100644 --- a/templates/layout/header.html.twig +++ b/templates/layout/header.html.twig @@ -25,7 +25,7 @@ data-target="lgd-header__nav--secondary" aria-controls="lgd-header__nav--secondary" aria-expanded="false" - aria-label="{{ 'Services: expand and jump to services menu'|t }}" + aria-label="{{ 'Services: jump to services'|t }}" > {{ 'Services'|t }} diff --git a/templates/layout/page.html.twig b/templates/layout/page.html.twig index 2dd4d5a0..f6b5ac09 100644 --- a/templates/layout/page.html.twig +++ b/templates/layout/page.html.twig @@ -98,7 +98,7 @@ {{ page.messages }} -
{# The "skip to content" link jumps to here. #} +
{# The "skip to content" link jumps to here. #} {% if has_content_top %} {{ page.content_top }} @@ -171,6 +171,15 @@ {% endif %}
+{% if back_to_top %} + {# + Position the link just after
at the end so that it's in about the + right place, *and* to try to minimize the chances of being rendered inside + a positioned element. + #} + {% include "@localgov_base/_components/back-to-top.twig" %} +{% endif %} + {# Begin Footer Regions #} {% block footer_sections %} {% if footer_regions %} diff --git a/templates/navigation/book-navigation--publication.html.twig b/templates/navigation/book-navigation--publication.html.twig new file mode 100644 index 00000000..c5cdb4b3 --- /dev/null +++ b/templates/navigation/book-navigation--publication.html.twig @@ -0,0 +1,41 @@ +{# +/** + * @file + * Default theme implementation to navigate books. + * + * Presented under nodes that are a part of book outlines. + * + * Available variables: + * - tree: The immediate children of the current node rendered as an unordered + * list. + * - current_depth: Depth of the current node within the book outline. Provided + * for context. + * - prev_url: URL to the previous node. + * - prev_title: Title of the previous node. + * - parent_url: URL to the parent node. + * - parent_title: Title of the parent node. Not printed by default. Provided + * as an option. + * - next_url: URL to the next node. + * - next_title: Title of the next node. + * - has_links: Flags TRUE whenever the previous, parent or next data has a + * value. + * - book_id: The book ID of the current outline being viewed. Same as the node + * ID containing the entire outline. Provided for context. + * - book_url: The book/node URL of the current outline being viewed. Provided + * as an option. Not used by default. + * - book_title: The book/node title of the current outline being viewed. + * + * @see template_preprocess_book_navigation() + * + * @ingroup themeable + */ +#} + +{% if has_links %} + {# This uses previous_* vars, prev-next uses prev_*, so we translate. #} + {% set previous_url = prev_url %} + {% set previous_title = prev_title %} + {% set show_title = 1 %} + {% set prev_next_type = 'publications' %} + {% include '@localgov_base/_components/prev-next.twig' %} +{% endif %} diff --git a/templates/navigation/breadcrumb.html.twig b/templates/navigation/breadcrumb.html.twig index 970edb49..02e4949f 100644 --- a/templates/navigation/breadcrumb.html.twig +++ b/templates/navigation/breadcrumb.html.twig @@ -5,6 +5,7 @@ * * Available variables: * - breadcrumb: Breadcrumb trail items. + * - breadcrumb_token: Used to insert a token to be replaced by a reverse CMS (EG Civica Modern.Gov) */ #} @@ -25,6 +26,9 @@ {% endif %} {% endfor %} + {% if breadcrumb_token is defined %} + {{ breadcrumb_token }} + {% endif %} {% endif %} diff --git a/templates/paragraphs/paragraph--localgov-fact-box.html.twig b/templates/paragraphs/paragraph--localgov-fact-box.html.twig index 16d0dbff..a6a7519d 100644 --- a/templates/paragraphs/paragraph--localgov-fact-box.html.twig +++ b/templates/paragraphs/paragraph--localgov-fact-box.html.twig @@ -66,14 +66,14 @@ {{ paragraph.localgov_above_text.value }}

{% endif %} - +

- {{ content.localgov_fact }} + {{ paragraph.localgov_fact.value }}

{{ content|without('localgov_above_text', 'localgov_fact', 'localgov_below_text', 'localgov_background') }}
- + {% if paragraph.localgov_below_text.value %}

{{ paragraph.localgov_below_text.value }} diff --git a/templates/views/views-view-list--localgov-step-by-step-navigation--prev-next.html.twig b/templates/views/views-view-list--localgov-step-by-step-navigation--prev-next.html.twig index 8f9d6cd4..8015351f 100644 --- a/templates/views/views-view-list--localgov-step-by-step-navigation--prev-next.html.twig +++ b/templates/views/views-view-list--localgov-step-by-step-navigation--prev-next.html.twig @@ -42,44 +42,8 @@ {% set previous_icon = 'chevron-left' %} {% set next_icon = 'chevron-right' %} +{% set prev_next_type = 'step_by_step' %} +{% set previous_url = has_prev_step ? path('entity.node.canonical', {'node': prev_step_nid }) : '' %} +{% set next_url = has_next_step ? path('entity.node.canonical', {'node': next_step_nid }) : '' %} -

+{% include '@localgov_base/_components/prev-next.twig' %}