Skip to content

Commit

Permalink
Skip link improvements (#13)
Browse files Browse the repository at this point in the history
* Add JS to skip links to focus on content
  • Loading branch information
ahosgood authored Oct 9, 2023
1 parent 7406893 commit dba7b75
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ module.exports = {
return config;
},
docs: {
autodocs: false,
autodocs: true,
},
};
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `<small>` elements are now styled
- Some basic print styles added for tabs, breadcrumbs and cookie banners
- Grids can be centre aligned with `tna-container--centred`
- Skip links will focus on the targeted element with JavaScript once the skip link is clicked

### Changed

Expand Down
7 changes: 7 additions & 0 deletions src/nationalarchives/all.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Gallery } from "./components/gallery/gallery.mjs";
import { Header } from "./components/header/header.mjs";
import { Picture } from "./components/picture/picture.mjs";
import { SensitiveImage } from "./components/sensitive-image/sensitive-image.mjs";
import { SkipLink } from "./components/skip-link/skip-link.mjs";
import { Tabs } from "./components/tabs/tabs.mjs";
import Cookies from "./lib/cookies.mjs";

Expand Down Expand Up @@ -81,6 +82,11 @@ const initAll = (options) => {
new SensitiveImage($sensitiveImage).init();
});

const $skipLinks = $scope.querySelectorAll('[data-module="tna-skip-link"]');
$skipLinks.forEach(($skipLink) => {
new SkipLink($skipLink).init();
});

const $tabs = $scope.querySelectorAll('[data-module="tna-tabs"]');
$tabs.forEach(($tabModule) => {
new Tabs($tabModule).init();
Expand All @@ -96,5 +102,6 @@ export {
Header,
Picture,
SensitiveImage,
SkipLink,
Tabs,
};
2 changes: 1 addition & 1 deletion src/nationalarchives/components/skip-link/fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"name": "minimal",
"options": {},
"html": "<a href=\"#main-content\" class=\"tna-skip-link \">Skip to main content</a>",
"html": "<a href=\"#main-content\" class=\"tna-skip-link \" data-module=\"tna-skip-link\">Skip to main content</a>",
"hidden": false
}
]
Expand Down
40 changes: 40 additions & 0 deletions src/nationalarchives/components/skip-link/skip-link.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export class SkipLink {
linkedElementListener = false;

constructor($module) {
this.$module = $module;
this.linkedElementId = $module.getAttribute("href").split("#").pop();
this.$linkedElement =
$module &&
this.linkedElementId &&
document.getElementById(this.linkedElementId);
}

init() {
if (!this.$module || !this.$linkedElement) {
return;
}
this.$module.addEventListener("click", () => this.focusLinkedElement());
}

focusLinkedElement() {
if (!this.$linkedElement.getAttribute("tabindex")) {
this.$linkedElement.setAttribute("tabindex", "-1");
this.$linkedElement.classList.add("tna-!--no-focus-style");

if (!this.linkedElementListener) {
this.$linkedElement.addEventListener("blur", () =>
this.removeFocusProperties(),
);
this.linkedElementListener = true;
}
}

this.$linkedElement.focus();
}

removeFocusProperties() {
this.$linkedElement.removeAttribute("tabindex");
this.$linkedElement.classList.remove("tna-!--no-focus-style");
}
}
34 changes: 18 additions & 16 deletions src/nationalarchives/components/skip-link/skip-link.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
width: 1px !important;
height: 1px !important;
margin: 0 !important;
padding: 1rem !important;
padding: 0 !important;

display: block !important;

position: absolute !important;
left: -100vw;

overflow: hidden !important;

Expand All @@ -25,11 +26,20 @@
@include colour.colour-font("button-text", $important: true);
}

&:focus {
@include colour.colour-background("button-background", $important: true);

@include colour.colour-border("focus-outline", 0.3125rem, $important: true);

outline: none !important;
}

&:active,
&:focus {
width: auto !important;
height: auto !important;
margin: inherit !important;
padding: 1rem !important;

position: static !important;

Expand All @@ -40,22 +50,14 @@
clip: auto !important;
-webkit-clip-path: none !important;
clip-path: none !important;
}

&:focus {
@include colour.colour-background("button-background", $important: true);

@include colour.colour-border("focus-outline", 0.3125rem, $important: true);

outline: none !important;
}

&:hover {
@include colour.colour-font("button-hover-text", $important: true);
&:hover {
@include colour.colour-font("button-hover-text", $important: true);

@include colour.colour-background(
"button-hover-background",
$important: true
);
@include colour.colour-background(
"button-hover-background",
$important: true
);
}
}
}
45 changes: 44 additions & 1 deletion src/nationalarchives/components/skip-link/skip-link.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import SkipLink from "./template.njk";
import macroOptions from "./macro-options.json";
import { expect } from "@storybook/jest";
import { within, userEvent } from "@storybook/testing-library";

const argTypes = {
text: { control: "text" },
Expand All @@ -23,9 +25,50 @@ const Template = ({ text, href, classes, attributes }) =>
`<p>To view the skip link component tab to this example, or click inside this example and press tab.</p>
${SkipLink({
params: { text, href, classes, attributes },
})}`;
})}
<main id="main-content" role="main">
<h1>Main content</h1>
</main>`;

export const Standard = Template.bind({});
Standard.args = {
classes: "tna-skip-link--demo",
};

export const Test = Template.bind({});
Test.args = {
text: "Skip to main content",
href: "main-content",
classes: "tna-skip-link--demo",
};
Test.play = async ({ args, canvasElement, step }) => {
const canvas = within(canvasElement);

const $skipLink = canvas.getByText(args.text);
const $linkedElement = document.getElementById(args.href);

await expect($skipLink.getBoundingClientRect().width).toBe(1);
await expect($skipLink.getBoundingClientRect().height).toBe(1);
await expect($skipLink.getBoundingClientRect().x).toBeLessThanOrEqual(-1);
await expect($skipLink).not.toHaveFocus();
await expect($linkedElement).not.toHaveFocus();
await expect($linkedElement).not.toHaveAttribute("tabindex");

await userEvent.keyboard("[Tab]");
await expect($skipLink).toHaveFocus();
await expect($skipLink.getBoundingClientRect().width).toBeGreaterThan(1);
await expect($skipLink.getBoundingClientRect().height).toBeGreaterThan(1);
await expect($skipLink.getBoundingClientRect().x).toBeGreaterThanOrEqual(0);

await userEvent.click($skipLink);
await expect($skipLink.getBoundingClientRect().width).toBe(1);
await expect($skipLink.getBoundingClientRect().height).toBe(1);
await expect($skipLink.getBoundingClientRect().x).toBeLessThanOrEqual(-1);
await expect($skipLink).not.toHaveFocus();
await expect($linkedElement).toHaveAttribute("tabindex");
await expect($linkedElement).toHaveFocus();

await userEvent.keyboard("[Tab]");
await expect($linkedElement).not.toHaveFocus();
await expect($linkedElement).not.toHaveAttribute("tabindex");
};
2 changes: 1 addition & 1 deletion src/nationalarchives/components/skip-link/template.njk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{%- set containerClasses = [params.classes] if params.classes else [] -%}
<a href="#{{ params.href if params.href else 'main-content' }}" class="tna-skip-link {{ containerClasses | join(' ') }}" {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
<a href="#{{ params.href if params.href else 'main-content' }}" class="tna-skip-link {{ containerClasses | join(' ') }}" data-module="tna-skip-link" {%- for attribute, value in params.attributes %} {{ attribute }}="{{ value }}"{% endfor %}>
{{ params.text if params.text else 'Skip to main content' }}
</a>
6 changes: 6 additions & 0 deletions src/nationalarchives/utilities/_a11y.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@
@include colour.colour-outline("focus-outline", 0.3125rem, solid);
outline-offset: 0.125rem;
}

.tna-\!--no-focus-style {
&:focus {
outline: none;
}
}
2 changes: 1 addition & 1 deletion tasks/test-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const checkExists = [
...componentFiles("picture", "Picture"),
...componentFiles("profile"),
...componentFiles("sensitive-image", "SensitiveImage"),
...componentFiles("skip-link"),
...componentFiles("skip-link", "SkipLink"),
...componentFiles("tabs", "Tabs"),
// Tools
"nationalarchives/tools/_index.scss",
Expand Down

0 comments on commit dba7b75

Please sign in to comment.