1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2026-04-17 09:28:43 -04:00

custom <Image /> wrapper now supports static imports too

This commit is contained in:
2022-01-24 08:57:21 -05:00
parent 5d402bc31b
commit 51ecae3c9b
22 changed files with 289 additions and 355 deletions

View File

@@ -7,7 +7,7 @@ import type { ImageProps as NextImageProps } from "next/image";
import styles from "./Figure.module.css";
type Props = Omit<NextImageProps, "alt"> & {
children: ReactNode; // caption (can be in markdown, yay!!!)
children: ReactNode;
alt?: string; // becomes optional -- pulled from plaintext-ified caption if missing
className?: string;
};

View File

@@ -21,6 +21,10 @@
line-height: 2.3;
}
.nextjs:hover {
color: var(--medium);
}
.view_source {
padding-bottom: 2px;
border-bottom: 1px solid;
@@ -31,7 +35,7 @@
border-color: var(--kinda-light);
}
.beat {
.heart {
display: inline-block;
animation: beat 10s infinite; /* 6 bpm, call 911 if you see this please. */
animation-delay: 7.5s; /* offset from wave animation */

View File

@@ -8,7 +8,7 @@ import styles from "./Footer.module.css";
const Footer = () => (
<footer className={styles.footer}>
<div className={styles.row}>
<div className={styles.copyright}>
<div className={styles.license}>
Content{" "}
<Link href="/license/" prefetch={false}>
<a title="Creative Commons Attribution 4.0 International">licensed under CC-BY-4.0</a>
@@ -21,11 +21,12 @@ const Footer = () => (
</div>
<div className={styles.powered_by}>
Made with{" "}
<span className={styles.beat} title="Love">
<span className={styles.heart} title="Love">
<HeartIcon />
</span>{" "}
and{" "}
<a
className={styles.nextjs}
href="https://nextjs.org/"
title="Powered by Next.js"
aria-label="Next.js"
@@ -36,9 +37,9 @@ const Footer = () => (
</a>
.{" "}
<a
className={styles.view_source}
href={`https://github.com/${config.githubRepo}`}
title="View Source on GitHub"
className={styles.view_source}
target="_blank"
rel="noopener noreferrer"
>

View File

@@ -21,26 +21,13 @@
margin: 0 auto;
}
.nav > div {
line-height: 0;
}
.name {
flex: 1;
}
@media screen and (max-width: 768px) {
.header {
padding: 0.75em 1.25em;
height: 5.9em;
}
.name {
flex: 0;
}
.menu {
flex: 1;
max-width: 325px;
margin-left: 2.5em;
}

View File

@@ -1,5 +1,5 @@
import { memo } from "react";
import Name from "../Name/Name";
import Selfie from "../Selfie/Selfie";
import Menu from "../Menu/Menu";
import styles from "./Header.module.css";
@@ -7,13 +7,8 @@ import styles from "./Header.module.css";
const Header = () => (
<header className={styles.header}>
<nav className={styles.nav}>
<div className={styles.name}>
<Name />
</div>
<div className={styles.menu}>
<Menu />
</div>
<Selfie className={styles.selfie} />
<Menu className={styles.menu} />
</nav>
</header>
);

View File

@@ -3,8 +3,11 @@
margin-bottom: 0.5em;
line-height: 1.5;
/* offset (approximately) with sticky header so jumped-to content isn't hiding behind it */
scroll-margin-top: 4em;
/**
* offset (approximately) with sticky header so jumped-to content isn't hiding behind it.
* note: use rem so it isn't based on the heading's font size.
*/
scroll-margin-top: 5.5rem;
}
/* special bottom border for <h2>s */
@@ -13,11 +16,6 @@
border-bottom: 1px solid var(--kinda-light);
}
.h3,
.h4 {
scroll-margin-top: 5em;
}
/* sub-heading anchor styles */
.anchor {
margin: 0 0.25em;
@@ -38,12 +36,7 @@
}
@media screen and (max-width: 768px) {
.h2 {
scroll-margin-top: 5em;
}
.h3,
.h4 {
scroll-margin-top: 6em;
.heading {
scroll-margin-top: 6.5rem;
}
}

View File

@@ -4,20 +4,34 @@ import type { ImageProps as NextImageProps } from "next/image";
import styles from "./Image.module.css";
const Image = ({ src, width, height, alt, quality, priority, className, ...rest }: NextImageProps) => {
const Image = ({ src, width, height, placeholder, alt, quality, priority, className, ...rest }: NextImageProps) => {
// passed directly into next/image: https://nextjs.org/docs/api-reference/next/image
const imageProps: Partial<NextImageProps> = {
width,
height,
layout: "intrinsic",
alt: alt || "",
quality: quality || 65,
loading: priority ? "eager" : "lazy",
priority: !!priority,
};
if (typeof src === "object") {
// static image imports: extract variables from the src object
const staticImg = src as StaticImageData;
imageProps.src = staticImg;
// default to blur placeholder while loading
imageProps.placeholder = placeholder || (staticImg.blurDataURL ? "blur" : "empty");
} else {
// regular path to jpg/png/etc. passed in, which makes explicit width and height required
imageProps.src = (src as string).replace(/^\/public/g, "");
}
return (
<div className={classNames(styles.wrapper, className)}>
<NextImage
src={(src as string).replace(/^\/public/g, "")}
layout="intrinsic"
width={width}
height={height}
alt={alt || ""}
quality={quality || 65}
loading={priority ? "eager" : "lazy"}
priority={!!priority}
{...rest}
/>
{/* @ts-ignore */}
<NextImage {...imageProps} {...rest} />
</div>
);
};

View File

@@ -4,35 +4,35 @@
margin: 0;
}
.menu li {
.menu_item {
list-style: none;
display: inline-flex;
margin-left: 1.8em;
}
.menu li .link {
.menu_item .link {
display: inline-flex;
align-items: center;
color: var(--medium-dark);
}
.menu li .link:hover {
.menu_item .link:hover {
color: var(--link);
}
.menu li .icon {
.menu_item .icon {
width: 1.6em;
height: 1.6em;
}
.menu li span {
.menu_item .label {
font-size: 0.95em;
font-weight: 500;
margin-left: 0.8em;
line-height: 1;
}
.menu li.theme_toggle {
.menu_item.theme_toggle {
margin-left: 1.25em;
}
@@ -42,28 +42,28 @@
justify-content: space-between;
}
.menu li {
.menu_item {
margin-left: 0;
}
.menu li .icon {
.menu_item .icon {
width: 1.8em;
height: 1.8em;
}
/* hide text next to emojis on mobile */
.menu li span {
.menu_item .label {
display: none;
}
.menu li.theme_toggle {
.menu_item.theme_toggle {
margin-left: -0.3em;
}
}
/* the home icon is redundant when space is SUPER tight */
@media screen and (max-width: 380px) {
.menu li:first-of-type {
.menu_item:first-of-type {
display: none;
}
}

View File

@@ -6,6 +6,10 @@ import { HomeIcon, NotesIcon, ProjectsIcon, ContactIcon } from "../Icons";
import styles from "./Menu.module.css";
type Props = {
className?: string;
};
const links = [
{
icon: <HomeIcon className={classNames("icon", styles.icon)} />,
@@ -29,19 +33,19 @@ const links = [
},
];
const Menu = () => (
<ul className={styles.menu}>
const Menu = ({ className }: Props) => (
<ul className={classNames(styles.menu, className)}>
{links.map((link, index) => (
<li key={index}>
<li key={index} className={styles.menu_item}>
<Link href={link.href} prefetch={false}>
<a className={styles.link}>
{link.icon} <span>{link.text}</span>
{link.icon} <span className={styles.label}>{link.text}</span>
</a>
</Link>
</li>
))}
<li className={styles.theme_toggle}>
<li className={classNames(styles.theme_toggle, styles.menu_item)}>
<ThemeToggle className={styles.icon} />
</li>
</ul>

View File

@@ -45,7 +45,7 @@
}
.tags .tag::before {
content: "#"; /* cosmetically hashtagify tags */
content: "\0023"; /* cosmetically hashtagify tags */
padding-right: 0.125em;
color: var(--light);
}

View File

@@ -1,14 +1,19 @@
import { memo } from "react";
import Link from "next/link";
import Image from "next/image";
import classNames from "classnames";
import styles from "./Name.module.css";
import styles from "./Selfie.module.css";
import meJpg from "../../public/static/images/me.jpg";
const Name = () => (
type Props = {
className?: string;
};
const Selfie = ({ className }: Props) => (
<Link href="/">
<a className={styles.link}>
<a className={classNames(styles.link, className)}>
<div className={styles.selfie}>
<Image src={meJpg} alt="Photo of Jake Jarvis" width={70} height={70} quality={60} layout="intrinsic" priority />
</div>
@@ -17,4 +22,4 @@ const Name = () => (
</Link>
);
export default memo(Name);
export default memo(Selfie);