mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-22 17:23:22 +01:00
accessibility fixes
This commit is contained in:
parent
93fa06bf1b
commit
20b285a760
@ -13,13 +13,13 @@ export default function Availability() {
|
||||
|
||||
return (
|
||||
<LazyMotion features={domAnimation}>
|
||||
<m.aside
|
||||
<m.section
|
||||
variants={moveInBottom}
|
||||
className={className}
|
||||
{...getAnimationProps(shouldReduceMotion)}
|
||||
>
|
||||
<p dangerouslySetInnerHTML={{ __html: html }} />
|
||||
</m.aside>
|
||||
</m.section>
|
||||
</LazyMotion>
|
||||
)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ export default function Footer() {
|
||||
return (
|
||||
<footer className={`h-card ${styles.footer}`}>
|
||||
<LogoUnit small />
|
||||
<Networks small />
|
||||
<Networks label="Networks Footer" small />
|
||||
|
||||
<p className={styles.actions}>
|
||||
<Vcard />
|
||||
|
@ -17,7 +17,7 @@ export default function Header({ small }: Props) {
|
||||
return (
|
||||
<header className={`${styles.header} ${small ? styles.small : ''}`}>
|
||||
<LogoUnit small={small} />
|
||||
{!small ? <Networks /> : null}
|
||||
{!small ? <Networks label="Networks" /> : null}
|
||||
<div className={styles.meta}>
|
||||
{!small ? (
|
||||
<Suspense>
|
||||
|
24
src/components/Location/Flag.tsx
Normal file
24
src/components/Location/Flag.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -1,24 +1,9 @@
|
||||
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 { useLocation } from '../../hooks/useLocation'
|
||||
import RelativeTime from '@yaireo/relative-time'
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
import { Flag } from './Flag'
|
||||
|
||||
export default function Location() {
|
||||
const { now, next } = useLocation()
|
||||
@ -28,23 +13,28 @@ export default function Location() {
|
||||
|
||||
return now?.city ? (
|
||||
<LazyMotion features={domAnimation}>
|
||||
<m.aside
|
||||
<m.section
|
||||
aria-label="Location"
|
||||
variants={moveInTop}
|
||||
className={styles.location}
|
||||
{...getAnimationProps(shouldReduceMotion)}
|
||||
>
|
||||
<Flag countryCode={now?.country_code} />
|
||||
<Flag country={{ code: now.country_code, name: now.country }} />
|
||||
{now?.city} <span>Now</span>
|
||||
<div className={styles.next}>
|
||||
{next?.city && (
|
||||
<>
|
||||
{isDifferentCountry && <Flag countryCode={next.country_code} />}
|
||||
{isDifferentCountry && (
|
||||
<Flag
|
||||
country={{ code: next.country_code, name: next.country }}
|
||||
/>
|
||||
)}
|
||||
{next.city}{' '}
|
||||
<span>{relativeTime.from(new Date(next.date_start))}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</m.aside>
|
||||
</m.section>
|
||||
</LazyMotion>
|
||||
) : null
|
||||
}
|
||||
|
@ -2,16 +2,22 @@ import Link from 'next/link'
|
||||
import Logo from '../../images/logo.svg'
|
||||
import styles from './index.module.css'
|
||||
import resume from '../../../_content/resume.json'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
type Props = {
|
||||
small?: boolean
|
||||
}
|
||||
|
||||
export default function LogoUnit({ small }: Props) {
|
||||
const router = useRouter()
|
||||
const { pathname } = router
|
||||
const isHome = pathname === '/'
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={`${styles.logounit} ${small ? styles.small : null}`}
|
||||
href="/"
|
||||
aria-current={isHome ? 'page' : null}
|
||||
>
|
||||
<Logo className={styles.logo} />
|
||||
<h1 className={`p-name ${styles.title}`}>
|
||||
|
@ -3,6 +3,7 @@ import Head from 'next/head'
|
||||
const MetaFavicons = () => {
|
||||
return (
|
||||
<Head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"></meta>
|
||||
{/*
|
||||
Stop the favicon madness
|
||||
https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs
|
||||
|
@ -4,7 +4,7 @@ import Meta from '.'
|
||||
describe('Meta', () => {
|
||||
it('renders without crashing', async () => {
|
||||
await act(async () => {
|
||||
render(<Meta title="Hello World" />, {
|
||||
render(<Meta title="Hello World" description="Hello" />, {
|
||||
container: document.head
|
||||
})
|
||||
})
|
||||
@ -13,7 +13,7 @@ describe('Meta', () => {
|
||||
|
||||
it('renders without crashing with slug', async () => {
|
||||
await act(async () => {
|
||||
render(<Meta title="Hello World" slug="hello" />, {
|
||||
render(<Meta title="Hello World" description="Hello" slug="hello" />, {
|
||||
container: document.head
|
||||
})
|
||||
})
|
||||
|
@ -9,7 +9,7 @@ const Meta = ({
|
||||
slug
|
||||
}: {
|
||||
title: string
|
||||
description?: string
|
||||
description: string
|
||||
image?: string
|
||||
slug?: string
|
||||
}) => {
|
||||
@ -24,7 +24,7 @@ const Meta = ({
|
||||
|
||||
{/* <!-- Essential META Tags --> */}
|
||||
<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:image" content={`${meta.url}/${image}`} />
|
||||
<meta property="og:url" content={url} />
|
||||
|
@ -9,7 +9,12 @@ export const NetworkLink = ({ name, url }: { name: string; url: string }) => {
|
||||
|
||||
return (
|
||||
<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} />
|
||||
<span className={styles.title}>{name}</span>
|
||||
</m.a>
|
||||
|
@ -3,13 +3,13 @@ import Networks from '.'
|
||||
|
||||
describe('Networks', () => {
|
||||
it('renders correctly from data file values', () => {
|
||||
const { container } = render(<Networks />)
|
||||
const { container } = render(<Networks label="Networks" />)
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
expect(container.firstChild.nodeName).toBe('ASIDE')
|
||||
expect(container.firstChild.nodeName).toBe('SECTION')
|
||||
})
|
||||
|
||||
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.querySelector('.small')).toBeInTheDocument()
|
||||
})
|
||||
|
@ -5,6 +5,7 @@ import { getAnimationProps } from '../Transitions'
|
||||
import { NetworkLink } from './NetworkLink'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
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 animationProps = getAnimationProps(shouldReduceMotion)
|
||||
|
||||
return (
|
||||
<LazyMotion features={domAnimation}>
|
||||
<m.aside
|
||||
<m.section
|
||||
aria-label={label}
|
||||
variants={containerVariants}
|
||||
{...animationProps}
|
||||
className={small ? styles.small : styles.networks}
|
||||
@ -41,7 +43,7 @@ export default function Networks({ small }: Props) {
|
||||
url={profile.url}
|
||||
/>
|
||||
))}
|
||||
</m.aside>
|
||||
</m.section>
|
||||
</LazyMotion>
|
||||
)
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ export default function ProjectLinks({
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.projectLinks}>
|
||||
<h3 className={styles.title}>
|
||||
<h2 className={styles.title}>
|
||||
Links <span>Learn more on the interwebz.</span>
|
||||
</h3>
|
||||
</h2>
|
||||
|
||||
<ul>
|
||||
{links.map(({ title, url, icon }) => (
|
||||
|
@ -2,9 +2,9 @@ import styles from './index.module.css'
|
||||
|
||||
const ProjectTechstack = ({ techstack }: { techstack: string[] }) => (
|
||||
<div className={styles.projectTechstack}>
|
||||
<h3 className={styles.title}>
|
||||
<h2 className={styles.title}>
|
||||
Tools & Technologies <span>The tech stack I was involved with.</span>
|
||||
</h3>
|
||||
</h2>
|
||||
<ul>
|
||||
{techstack.map((tech) => (
|
||||
<li key={tech}>{tech}</li>
|
||||
|
@ -45,7 +45,7 @@ export default function Project({ project }: { project: ProjectType }) {
|
||||
<ProjectImage
|
||||
className={styles.fullContainer}
|
||||
image={image}
|
||||
alt={title}
|
||||
alt={`Showcase image no. ${i + 1} for ${title}`}
|
||||
key={i}
|
||||
sizes="100vw"
|
||||
// give priority to the first image
|
||||
|
@ -20,13 +20,13 @@ export default function ProjectPreview({
|
||||
<Link href={`/${slug}`} className={styles.project} key={slug}>
|
||||
<ProjectImage
|
||||
image={image}
|
||||
alt={title}
|
||||
alt={`Showcase image for ${title}`}
|
||||
sizes="(max-width: 1090px) 100vw, 40vw"
|
||||
priority={imagePriority}
|
||||
/>
|
||||
|
||||
<footer className={styles.meta}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
<h2 className={styles.title}>{title}</h2>
|
||||
</footer>
|
||||
</Link>
|
||||
)
|
||||
|
@ -8,7 +8,7 @@ type Props = {
|
||||
|
||||
export default function Projects({ projects }: Props) {
|
||||
return (
|
||||
<section className={styles.projects}>
|
||||
<nav className={styles.projects}>
|
||||
{projects.length > 0 &&
|
||||
projects.map((project, i) => (
|
||||
<ProjectPreview
|
||||
@ -20,6 +20,6 @@ export default function Projects({ projects }: Props) {
|
||||
slug={project.slug}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
@ -7,5 +7,5 @@
|
||||
|
||||
.repos {
|
||||
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));
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ describe('Repository', () => {
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
})
|
||||
|
@ -17,9 +17,9 @@ export default function Repository({ repo }: { repo: Repo }) {
|
||||
|
||||
return (
|
||||
<div className={styles.repo}>
|
||||
<h1 className={styles.repoTitle}>
|
||||
<h3 className={styles.repoTitle}>
|
||||
<a href={repoLink}>{isExternal ? full_name : name}</a>
|
||||
</h1>
|
||||
</h3>
|
||||
<p>{description}</p>
|
||||
<p className={styles.meta}>
|
||||
{name === 'portfolio' || name === 'blog'
|
||||
@ -35,7 +35,10 @@ export default function Repository({ repo }: { repo: Repo }) {
|
||||
<Icon name="GitHub" /> GitHub
|
||||
</a>
|
||||
|
||||
<a href={`${html_url}/stargazers`}>
|
||||
<a
|
||||
aria-label={`${stargazers_count} stars`}
|
||||
href={`${html_url}/stargazers`}
|
||||
>
|
||||
<Icon name="Star" /> {stargazers_count}
|
||||
</a>
|
||||
</p>
|
||||
|
@ -17,7 +17,7 @@ export default function ThemeSwitch() {
|
||||
content="black-translucent"
|
||||
/>
|
||||
</Head>
|
||||
<aside className={styles.themeSwitch}>
|
||||
<aside aria-label="Theme Switch" className={styles.themeSwitch}>
|
||||
<label className={styles.checkbox}>
|
||||
<span className={styles.label}>Toggle Night Mode</span>
|
||||
<ThemeToggleInput />
|
||||
|
@ -3,7 +3,7 @@ import Meta from '../../components/Meta'
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
title: string
|
||||
description?: string
|
||||
description: string
|
||||
image?: string
|
||||
slug?: string
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ import NotFound from '../components/404'
|
||||
import Page from '../layouts/Page'
|
||||
|
||||
const pageMeta = {
|
||||
title: `NotFound`
|
||||
title: `Shenanigans`,
|
||||
description: 'Page not found.'
|
||||
}
|
||||
|
||||
export default function NotFoundPage() {
|
||||
|
Loading…
Reference in New Issue
Block a user