diff --git a/cypress/integration/index.js b/cypress/integration/index.js index 10dae13..137a1e0 100644 --- a/cypress/integration/index.js +++ b/cypress/integration/index.js @@ -1,21 +1 @@ /// -describe("Accessibility checks", () => { - beforeEach(() => { - }) - it("Has no detectable a11y violations on load", () => { - cy.visit("http://localhost:8000") - cy.injectAxe() - cy.wait(500) - cy.checkA11y() - }) - it("Handles focus on route change via click", () => { - cy.visit("http://localhost:8000") - cy.focused() - .should("not.have.class", "routeSkipLink") - - cy.get('#page-navigation').find('a').eq(0).click() - - cy.focused() - .should("have.class", "routeSkipLink") - }) -}) \ No newline at end of file diff --git a/examples/client-side-routing/gatsby-browser.js b/examples/client-side-routing/gatsby-browser.js new file mode 100644 index 0000000..40441cd --- /dev/null +++ b/examples/client-side-routing/gatsby-browser.js @@ -0,0 +1,8 @@ +exports.onRouteUpdate = ({ location, prevLocation }) => { + if (prevLocation !== null) { + const skipLink = document.querySelector('.routeSkipLink') + if (skipLink) { + skipLink.focus() + } + } +} \ No newline at end of file diff --git a/examples/client-side-routing/header.js b/examples/client-side-routing/header.js new file mode 100644 index 0000000..1a3fe83 --- /dev/null +++ b/examples/client-side-routing/header.js @@ -0,0 +1,28 @@ +import { Link } from 'gatsby' +import PropTypes from 'prop-types' +import React from 'react' + +const Header = ({ siteTitle }) => ( +
+ +

+ + {siteTitle} + +

+
+) + +Header.propTypes = { + siteTitle: PropTypes.string, +} + +Header.defaultProps = { + siteTitle: '', +} + +export default Header diff --git a/examples/client-side-routing/page.jsx b/examples/client-side-routing/page.jsx new file mode 100644 index 0000000..e0109d1 --- /dev/null +++ b/examples/client-side-routing/page.jsx @@ -0,0 +1,26 @@ +import React from "react" + +import Layout from '../components/site-chrome/layout' +import SEO from '../components/site-chrome/seo' + +import RouteTargetHeading from "./route-target-heading.js" + +const HeadingDemoPage = () => { + return ( + + +
+ + Heading Demo + +
+
+ ) +} + +export default HeadingDemoPage + + diff --git a/examples/client-side-routing/route-target-heading.js b/examples/client-side-routing/route-target-heading.js new file mode 100644 index 0000000..b53636b --- /dev/null +++ b/examples/client-side-routing/route-target-heading.js @@ -0,0 +1,12 @@ +import React from "react" +import { css } from "@emotion/core" + +const styles = css` + +` +const RouteHeading = () => { + return ( + <> + ) +} +export default RouteHeading diff --git a/examples/dropdown/dropdown.js b/examples/dropdown/dropdown.js new file mode 100644 index 0000000..504f1c5 --- /dev/null +++ b/examples/dropdown/dropdown.js @@ -0,0 +1,69 @@ +import React, { useState, useRef, useEffect } from "react" +import uuid from "uuid" + +import "./dropdown.scss" + +const Dropdown = ({ activatorText = 'Dropdown', items = [] }) => { + const [isOpen, setIsOpen] = useState(false) + const activatorRef = useRef(null) + const dropdownListRef = useRef(null) + + const wrapKeyHandler = (event) => { + if (event.keyCode === 27 && isOpen) { + // escape key + setIsOpen(false) + activatorRef.current.focus() + } + } + const clickHandler = () => { + setIsOpen(!isOpen) + } + const clickOutsideHandler = (event) => { + if (dropdownListRef.current.contains(event.target) || activatorRef.current.contains(event.target)) { + return + } + setIsOpen() + } + useEffect(() => { + if (isOpen) { + document.addEventListener('mousedown', clickOutsideHandler) + + dropdownListRef.current.querySelector('a').focus() + } else { + document.removeEventListener('mousedown', clickOutsideHandler) + } + + return () => { + document.removeEventListener('mousedown', clickOutsideHandler) + } + }, [isOpen]) + return ( +
+ + +
+ ) +} +export default Dropdown diff --git a/examples/integration-testing/nav.spec.js b/examples/integration-testing/nav.spec.js new file mode 100644 index 0000000..8223ad1 --- /dev/null +++ b/examples/integration-testing/nav.spec.js @@ -0,0 +1,14 @@ +context("Nav menu", () => { + beforeEach(() => { + cy.visit(`https://marcysutton.github.io/js-a11y-workshop`) + cy.injectAxe() + cy.wait(100) + }) + it("has no accessibility violations on load", () => { + cy.checkA11y() + }) + it("has a focusable, labeled button", () => { + cy.get("[aria-label='Open menu']").focus() + cy.focused().should("have.attr", "aria-label") + }) +}) \ No newline at end of file diff --git a/examples/integration-testing/routing.js b/examples/integration-testing/routing.js new file mode 100644 index 0000000..10dae13 --- /dev/null +++ b/examples/integration-testing/routing.js @@ -0,0 +1,21 @@ +/// +describe("Accessibility checks", () => { + beforeEach(() => { + }) + it("Has no detectable a11y violations on load", () => { + cy.visit("http://localhost:8000") + cy.injectAxe() + cy.wait(500) + cy.checkA11y() + }) + it("Handles focus on route change via click", () => { + cy.visit("http://localhost:8000") + cy.focused() + .should("not.have.class", "routeSkipLink") + + cy.get('#page-navigation').find('a').eq(0).click() + + cy.focused() + .should("have.class", "routeSkipLink") + }) +}) \ No newline at end of file diff --git a/examples/live-region/async-form.js b/examples/live-region/async-form.js new file mode 100644 index 0000000..fb46280 --- /dev/null +++ b/examples/live-region/async-form.js @@ -0,0 +1,52 @@ +import React, {useState} from "react" +import {DebounceInput} from 'react-debounce-input' + +const AccessibleAsyncForm = () => { + const [message, setMessage] = useState(null) + const [updating, setUpdating] = useState(false) + + const handleSubmit = (event) => { + event.preventDefault() + } + const handleTextChange = (value) => { + setUpdating(true) + } + const dismissToast = () => { + setUpdating(false) + } + return ( +
+