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:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
.tags .tag::before {
|
||||
content: "#"; /* cosmetically hashtagify tags */
|
||||
content: "\0023"; /* cosmetically hashtagify tags */
|
||||
padding-right: 0.125em;
|
||||
color: var(--light);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user