1
0
mirror of https://github.com/kremalicious/portfolio.git synced 2024-12-22 17:23:22 +01:00

accessibility fixes

This commit is contained in:
Matthias Kretschmann 2022-11-17 23:46:24 +00:00
parent 93fa06bf1b
commit 20b285a760
Signed by: m
GPG Key ID: 606EEEF3C479A91F
23 changed files with 85 additions and 53 deletions

View File

@ -13,13 +13,13 @@ export default function Availability() {
return ( return (
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<m.aside <m.section
variants={moveInBottom} variants={moveInBottom}
className={className} className={className}
{...getAnimationProps(shouldReduceMotion)} {...getAnimationProps(shouldReduceMotion)}
> >
<p dangerouslySetInnerHTML={{ __html: html }} /> <p dangerouslySetInnerHTML={{ __html: html }} />
</m.aside> </m.section>
</LazyMotion> </LazyMotion>
) )
} }

View File

@ -11,7 +11,7 @@ export default function Footer() {
return ( return (
<footer className={`h-card ${styles.footer}`}> <footer className={`h-card ${styles.footer}`}>
<LogoUnit small /> <LogoUnit small />
<Networks small /> <Networks label="Networks Footer" small />
<p className={styles.actions}> <p className={styles.actions}>
<Vcard /> <Vcard />

View File

@ -17,7 +17,7 @@ export default function Header({ small }: Props) {
return ( return (
<header className={`${styles.header} ${small ? styles.small : ''}`}> <header className={`${styles.header} ${small ? styles.small : ''}`}>
<LogoUnit small={small} /> <LogoUnit small={small} />
{!small ? <Networks /> : null} {!small ? <Networks label="Networks" /> : null}
<div className={styles.meta}> <div className={styles.meta}>
{!small ? ( {!small ? (
<Suspense> <Suspense>

View File

@ -0,0 +1,24 @@
import styles from './index.module.css'
type Props = {
country: {
name: string
code: string
}
}
export function Flag({ country }: Props) {
if (!country?.name || !country?.code) return null
// offset between uppercase ascii and regional indicator symbols
const OFFSET = 127397
const emoji = country.code.replace(/./g, (char) =>
String.fromCodePoint(char.charCodeAt(0) + OFFSET)
)
return (
<span role="img" aria-label={country.name} className={styles.emoji}>
{emoji}
</span>
)
}

View File

@ -1,24 +1,9 @@
import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion' import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion'
import { moveInBottom, getAnimationProps, moveInTop } from '../Transitions' import { getAnimationProps, moveInTop } from '../Transitions'
import styles from './index.module.css' import styles from './index.module.css'
import { useLocation } from '../../hooks/useLocation' import { useLocation } from '../../hooks/useLocation'
import RelativeTime from '@yaireo/relative-time' import RelativeTime from '@yaireo/relative-time'
import { Flag } from './Flag'
function Flag({ countryCode }: { countryCode: string }) {
if (!countryCode) return null
// offset between uppercase ascii and regional indicator symbols
const OFFSET = 127397
const emoji = countryCode.replace(/./g, (char) =>
String.fromCodePoint(char.charCodeAt(0) + OFFSET)
)
return (
<span role="img" className={styles.emoji}>
{emoji}
</span>
)
}
export default function Location() { export default function Location() {
const { now, next } = useLocation() const { now, next } = useLocation()
@ -28,23 +13,28 @@ export default function Location() {
return now?.city ? ( return now?.city ? (
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<m.aside <m.section
aria-label="Location"
variants={moveInTop} variants={moveInTop}
className={styles.location} className={styles.location}
{...getAnimationProps(shouldReduceMotion)} {...getAnimationProps(shouldReduceMotion)}
> >
<Flag countryCode={now?.country_code} /> <Flag country={{ code: now.country_code, name: now.country }} />
{now?.city} <span>Now</span> {now?.city} <span>Now</span>
<div className={styles.next}> <div className={styles.next}>
{next?.city && ( {next?.city && (
<> <>
{isDifferentCountry && <Flag countryCode={next.country_code} />} {isDifferentCountry && (
<Flag
country={{ code: next.country_code, name: next.country }}
/>
)}
{next.city}{' '} {next.city}{' '}
<span>{relativeTime.from(new Date(next.date_start))}</span> <span>{relativeTime.from(new Date(next.date_start))}</span>
</> </>
)} )}
</div> </div>
</m.aside> </m.section>
</LazyMotion> </LazyMotion>
) : null ) : null
} }

View File

@ -2,16 +2,22 @@ import Link from 'next/link'
import Logo from '../../images/logo.svg' import Logo from '../../images/logo.svg'
import styles from './index.module.css' import styles from './index.module.css'
import resume from '../../../_content/resume.json' import resume from '../../../_content/resume.json'
import { useRouter } from 'next/router'
type Props = { type Props = {
small?: boolean small?: boolean
} }
export default function LogoUnit({ small }: Props) { export default function LogoUnit({ small }: Props) {
const router = useRouter()
const { pathname } = router
const isHome = pathname === '/'
return ( return (
<Link <Link
className={`${styles.logounit} ${small ? styles.small : null}`} className={`${styles.logounit} ${small ? styles.small : null}`}
href="/" href="/"
aria-current={isHome ? 'page' : null}
> >
<Logo className={styles.logo} /> <Logo className={styles.logo} />
<h1 className={`p-name ${styles.title}`}> <h1 className={`p-name ${styles.title}`}>

View File

@ -3,6 +3,7 @@ import Head from 'next/head'
const MetaFavicons = () => { const MetaFavicons = () => {
return ( return (
<Head> <Head>
<meta name="viewport" content="width=device-width,initial-scale=1"></meta>
{/* {/*
Stop the favicon madness Stop the favicon madness
https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs

View File

@ -4,7 +4,7 @@ import Meta from '.'
describe('Meta', () => { describe('Meta', () => {
it('renders without crashing', async () => { it('renders without crashing', async () => {
await act(async () => { await act(async () => {
render(<Meta title="Hello World" />, { render(<Meta title="Hello World" description="Hello" />, {
container: document.head container: document.head
}) })
}) })
@ -13,7 +13,7 @@ describe('Meta', () => {
it('renders without crashing with slug', async () => { it('renders without crashing with slug', async () => {
await act(async () => { await act(async () => {
render(<Meta title="Hello World" slug="hello" />, { render(<Meta title="Hello World" description="Hello" slug="hello" />, {
container: document.head container: document.head
}) })
}) })

View File

@ -9,7 +9,7 @@ const Meta = ({
slug slug
}: { }: {
title: string title: string
description?: string description: string
image?: string image?: string
slug?: string slug?: string
}) => { }) => {
@ -24,7 +24,7 @@ const Meta = ({
{/* <!-- Essential META Tags --> */} {/* <!-- Essential META Tags --> */}
<title>{title}</title> <title>{title}</title>
<meta name="description" content={description} /> <meta name="description" content={`${description.slice(0, 200)}...`} />
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:image" content={`${meta.url}/${image}`} /> <meta property="og:image" content={`${meta.url}/${image}`} />
<meta property="og:url" content={url} /> <meta property="og:url" content={url} />

View File

@ -9,7 +9,12 @@ export const NetworkLink = ({ name, url }: { name: string; url: string }) => {
return ( return (
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<m.a variants={moveInTop} className={linkClasses} href={url}> <m.a
aria-label={name}
variants={moveInTop}
className={linkClasses}
href={url}
>
<Icon name={name} /> <Icon name={name} />
<span className={styles.title}>{name}</span> <span className={styles.title}>{name}</span>
</m.a> </m.a>

View File

@ -3,13 +3,13 @@ import Networks from '.'
describe('Networks', () => { describe('Networks', () => {
it('renders correctly from data file values', () => { it('renders correctly from data file values', () => {
const { container } = render(<Networks />) const { container } = render(<Networks label="Networks" />)
expect(container.firstChild).toBeInTheDocument() expect(container.firstChild).toBeInTheDocument()
expect(container.firstChild.nodeName).toBe('ASIDE') expect(container.firstChild.nodeName).toBe('SECTION')
}) })
it('renders correctly in small variant', () => { it('renders correctly in small variant', () => {
const { container } = render(<Networks small={true} />) const { container } = render(<Networks label="Networks" small={true} />)
expect(container.firstChild).toBeInTheDocument() expect(container.firstChild).toBeInTheDocument()
expect(container.querySelector('.small')).toBeInTheDocument() expect(container.querySelector('.small')).toBeInTheDocument()
}) })

View File

@ -5,6 +5,7 @@ import { getAnimationProps } from '../Transitions'
import { NetworkLink } from './NetworkLink' import { NetworkLink } from './NetworkLink'
type Props = { type Props = {
label: string
small?: boolean small?: boolean
} }
@ -17,13 +18,14 @@ const containerVariants = {
} }
} }
export default function Networks({ small }: Props) { export default function Networks({ label, small }: Props) {
const shouldReduceMotion = useReducedMotion() const shouldReduceMotion = useReducedMotion()
const animationProps = getAnimationProps(shouldReduceMotion) const animationProps = getAnimationProps(shouldReduceMotion)
return ( return (
<LazyMotion features={domAnimation}> <LazyMotion features={domAnimation}>
<m.aside <m.section
aria-label={label}
variants={containerVariants} variants={containerVariants}
{...animationProps} {...animationProps}
className={small ? styles.small : styles.networks} className={small ? styles.small : styles.networks}
@ -41,7 +43,7 @@ export default function Networks({ small }: Props) {
url={profile.url} url={profile.url}
/> />
))} ))}
</m.aside> </m.section>
</LazyMotion> </LazyMotion>
) )
} }

View File

@ -9,9 +9,9 @@ export default function ProjectLinks({
}) { }) {
return ( return (
<div className={styles.projectLinks}> <div className={styles.projectLinks}>
<h3 className={styles.title}> <h2 className={styles.title}>
Links <span>Learn more on the interwebz.</span> Links <span>Learn more on the interwebz.</span>
</h3> </h2>
<ul> <ul>
{links.map(({ title, url, icon }) => ( {links.map(({ title, url, icon }) => (

View File

@ -2,9 +2,9 @@ import styles from './index.module.css'
const ProjectTechstack = ({ techstack }: { techstack: string[] }) => ( const ProjectTechstack = ({ techstack }: { techstack: string[] }) => (
<div className={styles.projectTechstack}> <div className={styles.projectTechstack}>
<h3 className={styles.title}> <h2 className={styles.title}>
Tools & Technologies <span>The tech stack I was involved with.</span> Tools & Technologies <span>The tech stack I was involved with.</span>
</h3> </h2>
<ul> <ul>
{techstack.map((tech) => ( {techstack.map((tech) => (
<li key={tech}>{tech}</li> <li key={tech}>{tech}</li>

View File

@ -45,7 +45,7 @@ export default function Project({ project }: { project: ProjectType }) {
<ProjectImage <ProjectImage
className={styles.fullContainer} className={styles.fullContainer}
image={image} image={image}
alt={title} alt={`Showcase image no. ${i + 1} for ${title}`}
key={i} key={i}
sizes="100vw" sizes="100vw"
// give priority to the first image // give priority to the first image

View File

@ -20,13 +20,13 @@ export default function ProjectPreview({
<Link href={`/${slug}`} className={styles.project} key={slug}> <Link href={`/${slug}`} className={styles.project} key={slug}>
<ProjectImage <ProjectImage
image={image} image={image}
alt={title} alt={`Showcase image for ${title}`}
sizes="(max-width: 1090px) 100vw, 40vw" sizes="(max-width: 1090px) 100vw, 40vw"
priority={imagePriority} priority={imagePriority}
/> />
<footer className={styles.meta}> <footer className={styles.meta}>
<h1 className={styles.title}>{title}</h1> <h2 className={styles.title}>{title}</h2>
</footer> </footer>
</Link> </Link>
) )

View File

@ -8,7 +8,7 @@ type Props = {
export default function Projects({ projects }: Props) { export default function Projects({ projects }: Props) {
return ( return (
<section className={styles.projects}> <nav className={styles.projects}>
{projects.length > 0 && {projects.length > 0 &&
projects.map((project, i) => ( projects.map((project, i) => (
<ProjectPreview <ProjectPreview
@ -20,6 +20,6 @@ export default function Projects({ projects }: Props) {
slug={project.slug} slug={project.slug}
/> />
))} ))}
</section> </nav>
) )
} }

View File

@ -7,5 +7,5 @@
.repos { .repos {
composes: projects from '../Projects/index.module.css'; composes: projects from '../Projects/index.module.css';
grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
} }

View File

@ -17,7 +17,7 @@ describe('Repository', () => {
} }
const { container } = render(<Repository repo={repo1 as Repo} />) const { container } = render(<Repository repo={repo1 as Repo} />)
expect(container.querySelector('h1 > a').getAttribute('href')).toBe( expect(container.querySelector('h3 > a').getAttribute('href')).toBe(
repo1.html_url repo1.html_url
) )
}) })

View File

@ -17,9 +17,9 @@ export default function Repository({ repo }: { repo: Repo }) {
return ( return (
<div className={styles.repo}> <div className={styles.repo}>
<h1 className={styles.repoTitle}> <h3 className={styles.repoTitle}>
<a href={repoLink}>{isExternal ? full_name : name}</a> <a href={repoLink}>{isExternal ? full_name : name}</a>
</h1> </h3>
<p>{description}</p> <p>{description}</p>
<p className={styles.meta}> <p className={styles.meta}>
{name === 'portfolio' || name === 'blog' {name === 'portfolio' || name === 'blog'
@ -35,7 +35,10 @@ export default function Repository({ repo }: { repo: Repo }) {
<Icon name="GitHub" /> GitHub <Icon name="GitHub" /> GitHub
</a> </a>
<a href={`${html_url}/stargazers`}> <a
aria-label={`${stargazers_count} stars`}
href={`${html_url}/stargazers`}
>
<Icon name="Star" /> {stargazers_count} <Icon name="Star" /> {stargazers_count}
</a> </a>
</p> </p>

View File

@ -17,7 +17,7 @@ export default function ThemeSwitch() {
content="black-translucent" content="black-translucent"
/> />
</Head> </Head>
<aside className={styles.themeSwitch}> <aside aria-label="Theme Switch" className={styles.themeSwitch}>
<label className={styles.checkbox}> <label className={styles.checkbox}>
<span className={styles.label}>Toggle Night Mode</span> <span className={styles.label}>Toggle Night Mode</span>
<ThemeToggleInput /> <ThemeToggleInput />

View File

@ -3,7 +3,7 @@ import Meta from '../../components/Meta'
type Props = { type Props = {
children: React.ReactNode children: React.ReactNode
title: string title: string
description?: string description: string
image?: string image?: string
slug?: string slug?: string
} }

View File

@ -2,7 +2,8 @@ import NotFound from '../components/404'
import Page from '../layouts/Page' import Page from '../layouts/Page'
const pageMeta = { const pageMeta = {
title: `NotFound` title: `Shenanigans`,
description: 'Page not found.'
} }
export default function NotFoundPage() { export default function NotFoundPage() {