Continuing from yesterday:
- move the header back to the root route
- improve meta tags on some routes
- add a custom favicon
- remove references to “uncategorized” category
- prevent Remix app from crashing when WordPress is down
New items:
- change “Previous” and “Next” text used for post navigation to “Older” and “Newer”
- Fix bug in Previous/Next (older/newer) post links!!!
- improve alignment of older/newer post links
Maybe I should stop there. What’s going on with previous/next navigation?
Previous/next navigation bug
$ git checkout -b fix_previous_next_navigation_bug
I’ll start by changing the navigation link’s text. Labeling the links with “Previous” and “Next” is throwing me off. The labels mean previous and next in time (older/newer.)
Here’s the “bug”:
That’s not really a bug 🙂 There are two posts on my dev site with the title “Modular Synths: Exploring the Possibilities of Sonic Architecture”. The content on my development site was written by ChatGPT. I used it to quickly generate some posts, then copied the same title and text into two WordPress posts. I’ll delete the duplicate post and make sure things are working as expected.
That seems to have fixed the issue 🙂
At the risk of creating an actual bug, I’m going to move the (newly renamed) older/newer navigation from the bottom of the /blog/posts
and /blog/category/$categorySlug/$postId/$slug
routes into a component. I’m wary of being too quick to make code “dry.” I think it’s justified for this case though.
$ touch app/components/OlderNewerPostsNavigation.tsx
Copying the relevant code from blog.$postId.$slug
into the new component, Typescript’s warnings tell me that the following props are going to have to be passed to the component:
previousTitle
previousSlug
previousId
nextTitle
nextSlug
nextId
Navigation links on category archive pages also require the a categorySlug
. I’ll use a basePath
prop (either /blog
or /blog/category/$categorySlug
) to pass the category slug.
Typescript isn’t speeding me up yet, but it’s starting to make sense. The OlderNewerPostNavigation
component uses this interface for its props:
export interface PostNavigationProps {
previousPost: Maybe<Post>;
nextPost: Maybe<Post>;
basePath: string;
}
Routes that use the component can import the type:
import type { PostNavigationProps } from "~/components/OlderNewerPostNavigation";
Then call the component with:
export default function BlogPost() {
// ...
const { post, categorySlug } = useLoaderData<typeof loader>();
// ...
const postNavigationProps: PostNavigationProps = {
previousPost: post?.previousPostInCategory,
nextPost: post?.nextPostInCategory,
basePath: categorySlug ? `/blog/category/${categorySlug}` : "/blog",
};
// ...
return (
// ...
<OlderNewerPostNavigation {...postNavigationProps} />
</div>
);
}
Here’s the full component:
OlderNewerPostNavigation.tsx
// app/components/OlderNewerPostNavigation.tsx
import { Link } from "@remix-run/react";
import type { Post } from "~/graphql/__generated__/graphql";
import { Maybe } from "graphql/jsutils/Maybe";
import { Icon } from "~/components/Icon";
export interface PostNavigationProps {
previousPost: Maybe<Post>;
nextPost: Maybe<Post>;
basePath: string;
}
export default function OlderNewerPostNavigation({
previousPost,
nextPost,
basePath,
}: PostNavigationProps) {
return (
<div className="my-3 flex justify-between flex-col min-[431px]:flex-row">
{previousPost?.title && previousPost?.slug && previousPost?.databaseId ? (
<div>
<Link
prefetch="intent"
to={`${basePath}/${previousPost.databaseId}/${previousPost.slug}`}
className="text-lg font-bold text-sky-700 hover:underline"
>
<div className="flex items-center">
{" "}
<Icon
key="arrow-left"
id="arrow-left"
x={-5}
className="text-slate-700 w-10 h-10 self-center"
/>{" "}
<div className="text-slate-700">Older</div>
</div>
{previousPost.title}
</Link>
</div>
) : null}
{nextPost?.title && nextPost?.slug && nextPost?.databaseId ? (
<div className="min-[431px]:text-right">
<Link
prefetch="intent"
to={`${basePath}/${nextPost.databaseId}/${nextPost.slug}`}
className="text-lg font-bold text-sky-700 hover:underline"
>
<div className="flex items-center min-[431px]:justify-end">
<div className="text-slate-700">Newer</div>
<Icon
key="arrow-right"
id="arrow-right"
x={5}
className="text-slate-700 inline w-10 h-10"
/>{" "}
</div>
{nextPost.title}
</Link>
</div>
) : null}
</div>
);
}
I’m undecided about changing the link text from “previous/next” to “older/newer.” I’ll leave it for a while and see if it makes sense.
Moving on, the next item on the list isn’t on the list:
Increase header menu button size
Last night I tested the site on my phone. The menu button is too small to click. The drop down menu is also too small. It’s difficult to select a single item from it.
$ git checkout -b fix_menu_icon_size
The fix was to add a height to the header’s inner container, then set the width and height of the hamburger icon to the container’s height. I’ll probably change this a few times, so I set height and width as variables:
const containerHeightClass: string = "h-14";
const hamburgerWidthClass: string = "w-14";
I also added an event listener to click events that occur outside of the details element. It’s used to trigger the menu to close:
useEffect(() => {
const detailsElement = document.getElementById("blog-nav");
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Node;
if (detailsElement && !detailsElement.contains(target)) {
detailsElement.removeAttribute("open");
}
};
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
}, []);
It works!
BlogHeader.tsx
// app/components/BlogHeader.tsx
import { Link, NavLink, useLocation } from "@remix-run/react";
import { useEffect } from "react";
import { Maybe } from "graphql/jsutils/Maybe";
import { type RootQueryToCategoryConnection } from "~/graphql/__generated__/graphql";
import { Icon } from "~/components/Icon";
interface BlogHeaderProps {
categories: Maybe<RootQueryToCategoryConnection>;
}
export default function BlogHeader({ categories }: BlogHeaderProps) {
const location = useLocation();
const containerHeightClass: string = "h-14";
const hamburgerWidthClass: string = "w-14";
useEffect(() => {
const detailsElement = document.getElementById("blog-nav");
if (detailsElement) {
detailsElement.removeAttribute("open");
}
}, [location]);
useEffect(() => {
const detailsElement = document.getElementById("blog-nav");
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as Node;
if (detailsElement && !detailsElement.contains(target)) {
detailsElement.removeAttribute("open");
}
};
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
}, []);
return (
<header className="bg-sky-800 text-slate-50 px-3 py-2 top-0 sticky overflow-visible">
<div
className={`flex justify-between items-center w-full max-w-screen-xl mx-auto relative ${containerHeightClass}`}
>
<h1>
<Link to="/" className="text-3xl">
Zalgorithm
</Link>
</h1>
<div className={`relative ${containerHeightClass}`}>
<details
className="cursor-pointer absolute top-0 right-0 z-10"
id="blog-nav"
>
<summary className="_no-triangle block absolute right-0 top-0 list-none">
<Icon
key="hamburger"
id="hamburger"
className={`text-slate-50 rounded hover:bg-sky-700 hover:outline hover:outline-sky-700 hover:outline-4 hover:outline-solid ${hamburgerWidthClass} ${containerHeightClass}`}
/>
</summary>
<ul className="bg-slate-50 text-slate-800 text-lg p-3 rounded relative top-12 right-3 shadow-lg w-56 divide-slate-300 divide-y">
<NavLink
key="posts"
to="/blog/posts"
className={({ isActive, isPending }) =>
`py-1 pl-1 block hover:bg-slate-200 ${
isPending
? "pending"
: isActive
? "text-sky-700 font-medium bg-slate-200"
: ""
}`
}
>
<li>all posts</li>
</NavLink>
{categories?.nodes
? categories.nodes.map((category, index) => (
<NavLink
key={index}
to={`/blog/category/${category.slug}`}
className={({ isActive, isPending }) =>
`py-1 pl-1 block hover:bg-slate-200 ${
isPending
? "pending"
: isActive
? "text-sky-700 font-medium bg-slate-200"
: ""
}`
}
>
<li>{category.name}</li>
</NavLink>
))
: ""}
</ul>
</details>
</div>
</div>
</header>
);
}
That’s good for today 🙂