feat: improve cv layout

This commit is contained in:
Aaron Yarborough 2025-06-22 16:55:34 +01:00
parent 6d6c468be7
commit 188edc999c
9 changed files with 224 additions and 73 deletions

View file

@ -23,6 +23,7 @@
"Sitecore",
"sportank",
"Umbraco",
"Yarborough",
"Yarbz"
],
"files.autoSave": "off",

View file

@ -1,6 +1,6 @@
{
"name": "www-aaronjy-me",
"version": "2.3.0",
"version": "2.4.0",
"private": true,
"type": "module",
"lint-staged": {

View file

@ -7,7 +7,7 @@ import pck from "../../../package.json";
function Footer() {
return (
<footer className={style.footer} data-testid="footer">
<footer className={`${style.footer} hide-print`} data-testid="footer">
<hr />
<nav>
<div>

View file

@ -5,7 +5,7 @@ import styles from "./Header.module.css";
function Header() {
return (
<header className={styles.header} data-testid="header">
<header className={`${styles.header} hide-print`} data-testid="header">
<nav>
<Link href="/">Home</Link>
{", "}

View file

@ -2,8 +2,11 @@ import React from "react";
import style from "./Resume.module.css";
import { markdownToHtml } from "@/services/content-service";
import { MDXClient } from "next-mdx-remote-client/csr";
import Link from "next/link";
function Resume({
introMdxSource,
competencies,
education,
certifications,
@ -12,7 +15,7 @@ function Resume({
}) {
return (
<div className={style.cv}>
<ol>
<ol className="hide-print">
<li>
<a href="#experience">Professional experience</a>
</li>
@ -30,57 +33,73 @@ function Resume({
</li>
</ol>
<div>
<h2 id="experience">Professional experience</h2>
{experience?.map((exp, i) => (
<div key={i}>
<WorkExperience
employer={exp.employer}
position={exp.position}
start={exp.start}
end={exp.end}
>
{markdownToHtml(exp.description)}
</WorkExperience>
{!!exp.skills?.length && (
<details>
<summary>Competencies</summary>
<>{exp.skills.sort().join(", ")}</>
</details>
)}
</div>
))}
<div className={style.about}>
<MDXClient {...introMdxSource} />
<span className="print-only">
See more at <a href="https://aaronjy.me/cv">aaronjy.me/cv</a>
</span>
</div>
</div>
<div className="sidebar">
<h2 id="competencies">Competencies</h2>
<ul>
{competencies
?.sort((a, b) => a.name > b.name)
.map((c, i) => (
<li key={i}>{c.name}</li>
))}
</ul>
<div className={style.cvContent}>
<div className={style.experience}>
<h2 id="experience">Professional experience</h2>
<h2 id="certifications">Certifications</h2>
<ul>
{certifications
?.sort((a, b) => a.name > b.name)
.map((c, i) => (
<li key={i}>{c.name}</li>
))}
</ul>
<h2 className="languages">Languages</h2>
<ul>
{languages?.sort().map((c, i) => (
<li key={i}>
{c.name} - {c.proficiency}
</li>
{experience?.map((exp, i) => (
<div key={i}>
<WorkExperience
employer={exp.employer}
position={exp.position}
start={exp.start}
end={exp.end}
>
{markdownToHtml(exp.description)}
</WorkExperience>
{!!exp.skills?.length && (
<details className="hide-print">
<summary>Competencies</summary>
<>{exp.skills.sort().join(", ")}</>
</details>
)}
</div>
))}
</ul>
</div>
<div className={style.sidebar}>
<h2 id="competencies">Competencies</h2>
<ul>
{competencies
?.sort((a, b) => a.name - b.name)
.map((c, i) => (
<li key={i}>{c.name}</li>
))}
</ul>
<h2 className="education">Education</h2>
<p>{education.name}</p>
<h2 id="certifications">Certifications</h2>
<ul>
{certifications
?.sort((a, b) => a.name > b.name)
.map((c, i) => (
<li key={i}>{c.name}</li>
))}
</ul>
<h2 className="languages">Languages</h2>
<ul>
{languages?.sort().map((c, i) => (
<li key={i}>
<strong>{c.name}</strong> - {c.proficiency}
</li>
))}
</ul>
<h2 className="education">Education</h2>
<ul>
{education
?.sort((a, b) => a.name - b.name)
.map((c, i) => (
<li key={i}>{c.name}</li>
))}
</ul>
</div>
</div>
</div>
);
@ -90,21 +109,48 @@ export default Resume;
function WorkExperience({ position, employer, start, end, children }) {
return (
<div className={style["work-experience"]}>
<div>
<h3 id={position}>
{position}
<br />
<small>{employer}</small>
</h3>
<small>
<time>{start}</time> - <time>{end}</time>
</small>
</div>
<div
data-test="children"
dangerouslySetInnerHTML={{ __html: children }}
/>
</div>
// <div className={style["work-experience"]}>
// <div>
// <h3 id={position}>
// {position}
// <br />
// <small>{employer}</small>
// </h3>
// <small>
// <time>{start}</time> - <time>{end}</time>
// </small>
// </div>
// <div
// data-test="children"
// dangerouslySetInnerHTML={{ __html: children }}
// />
// </div>
<table className={style.experienceTable}>
<tbody>
<tr>
<td>
<span id={position} className={style.position}>
{position}
</span>
<br />
<span className={style.employer}>{employer}</span>
</td>
<td className="text-right">
{/* <small> */}
<time>{start}</time> - <time>{end}</time>
{/* </small> */}
</td>
</tr>
<tr>
<td colSpan={2}>
<div
data-test="children"
dangerouslySetInnerHTML={{ __html: children }}
/>
</td>
</tr>
</tbody>
</table>
);
}

View file

@ -0,0 +1,61 @@
.experienceTable {
width: 100%;
}
.position {
font-weight: 700;
}
.cvContent {
display: flex;
flex-direction: row-reverse;
gap: 3rem;
position: relative;
}
.cvContent .experience {
flex-basis: 66.66%;
}
.cvContent .sidebar {
flex-basis: 33.33%;
position: sticky;
top: 10px;
left: 0px;
height: 1px;
/* Needed to make sticky work */
}
.about {
margin-bottom: 0;
}
.cv ol,
.cv ul {
padding: 0 1rem;
}
@media screen and (max-width: 768px) {
.cvContent {
display: block;
}
.cvContent .sidebar {
height: auto;
position: relative;
}
}
@media print {
.cvContent {
gap: 1rem;
}
.cvContent .experience {
flex-basis: 80%;
}
.cvContent .sidebar {
flex-basis: 20%;
}
}

View file

View file

@ -1,9 +1,11 @@
import DefaultLayout from "@/layouts/DefaultLayout/DefaultLayout";
import React from "react";
import { NextSeo } from "next-seo";
import Resume from "@/components/Resume/Resume";
import { fetchCV } from "@/services/content-service";
import { FailedFetchCVError } from "@/errors";
import DefaultLayout from "@/layouts/DefaultLayout/DefaultLayout";
import { fetchCV } from "@/services/content-service";
import { NextSeo } from "next-seo";
import style from "./cv.module.css";
import { serialize } from "next-mdx-remote-client/serialize";
export const Title = "CV";
@ -22,10 +24,20 @@ export async function getStaticProps() {
};
}
const { competencies, education, languages, certifications, experience } = cv;
const {
intro,
competencies,
education,
languages,
certifications,
experience,
} = cv;
const introMdxSource = await serialize({ source: intro });
return {
props: {
introMdxSource,
competencies,
education,
languages,
@ -37,6 +49,7 @@ export async function getStaticProps() {
}
export default function ResumePage({
introMdxSource,
competencies,
education,
certifications,
@ -51,9 +64,11 @@ export default function ResumePage({
title: Title,
}}
/>
<h1>{Title}</h1>
<h1 className="hide-print">{Title}</h1>
<h1 className={`${style.printHeading} print-only`}>Aaron Yarborough</h1>
<section>
<Resume
introMdxSource={introMdxSource}
competencies={competencies}
education={education}
certifications={certifications}

View file

@ -22,3 +22,31 @@ td:first-child {
.bold {
font-weight: 700;
}
.text-right {
text-align: right;
}
.print-only {
display: none;
}
@media print {
html {
padding-top: 1rem;
}
body {
font-size: 12px;
line-height: 1.3;
padding: 0;
}
.hide-print {
display: none;
}
.print-only {
display: initial;
}
}