Skip to content

Commit

Permalink
Improve website infrastructure (#3057)
Browse files Browse the repository at this point in the history
1. Upgrade tailwind to
[3.4](https://tailwindcss.com/blog/tailwindcss-v3-4) to include
additional `max-h-*` classes.
2. Upgrade nextjs to [13.5](https://nextjs.org/blog/next-13-5) to
include a fix for null useRef "Invalid hook call. Hooks can only be
called inside of the body of a function component." error. This version
also includes significant performance improvements to the dev
environment.
3. Add react-hooks eslint plugin.
4. Add support for title images in blog posts. This is tested in
WATonomous/infra-config#3034
  • Loading branch information
ben-z authored Aug 28, 2024
1 parent 8cfbc92 commit 19742ee
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 149 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module.exports = {
"next/core-web-vitals",
"prettier",
"plugin:mdx/recommended",
"plugin:react/recommended"
"plugin:react/recommended",
"plugin:react-hooks/recommended",
],
"ignorePatterns": [
// imported components from shadcn
Expand Down
44 changes: 39 additions & 5 deletions components/blog-post.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { allImages } from '@/build/fixtures/images';
import { userProfiles } from '@/lib/data';
import CommentSection from '@/components/giscus-comments';
import { websiteConfig } from '@/lib/data';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { userProfiles, websiteConfig } from '@/lib/data';
import { cn, dayjsTz } from '@/lib/utils';
import { GithubIcon, LinkIcon, LinkedinIcon, MailIcon, XIcon } from 'lucide-react';
import { useRouter } from 'next/router';
import { Link, useConfig } from "nextra-theme-docs";
import React from 'react';
import { SubscribeDialog } from './blog';
import Picture from './picture';
import { GithubIcon, LinkIcon, LinkedinIcon, MailIcon, XIcon } from 'lucide-react';
import { Separator } from './ui/separator';
import { SubscribeDialog } from './blog';

// Reference for styling: https://github.com/vercel/turbo/blob/22585c9dcc23eb010ab01f177394358af03210d7/docs/pages/blog/turbo-1-10-0.mdx

Expand Down Expand Up @@ -41,7 +45,7 @@ export function Avatar({ username }: { username: string }) {
return (
<div className="flex items-center flex-shrink-0 md:justify-start">
<div className="w-[32px] h-[32px]">
<Picture image={image} alt={username} className="w-full rounded-full" />
<Picture image={image} alt={username} imgClassName="w-full rounded-full" />
</div>
<dl className="ml-2 text-sm font-medium leading-4 text-left whitespace-no-wrap">
<dt className="sr-only">Name</dt>
Expand Down Expand Up @@ -90,8 +94,38 @@ export function BlogPostHeader() {
const { locale = websiteConfig.default_locale } = useRouter()
const dateObj = date && timezone && dayjsTz(date, timezone).toDate()

let titleImageComponent;
if (frontMatter.title_image) {
const titleImage = allImages[frontMatter.title_image];
if (!titleImage) {
throw new Error(`No image found for title_image: ${frontMatter.title_image}`);
}
const titleImageAttribution = frontMatter.title_image_attribution;
if (!titleImageAttribution) {
throw new Error(`No attribution found for title_image: ${frontMatter.title_image}`);
}

titleImageComponent = (
<Popover>
<PopoverTrigger className="block mx-auto my-8">
<Picture
image={titleImage}
alt={titleImageAttribution}
imgClassName='h-[33vh] min-h-64 max-h-96 w-auto object-contain'
/>
</PopoverTrigger>
<PopoverContent side='bottom' className="w-96 max-w-full">
<div className="text-xs text-center text-gray-500 dark:text-gray-400">
{titleImageAttribution}
</div>
</PopoverContent>
</Popover>
)
}

return (
<>
{titleImageComponent}
<h1 className="text-4xl font-bold tracking-tight text-slate-900 dark:text-slate-100">{title}</h1>
<Date>
{/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */}
Expand Down
112 changes: 72 additions & 40 deletions components/blog.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { allImages } from '@/build/fixtures/images';
import {
AlertDialog,
AlertDialogAction,
Expand All @@ -6,31 +7,32 @@ import {
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from "@/components/ui/alert-dialog"
} from "@/components/ui/alert-dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage
} from "@/components/ui/form"
import { websiteConfig } from '@/lib/data'
import { dayjsTz } from '@/lib/utils'
import { zodResolver } from "@hookform/resolvers/zod"
import { useRouter } from 'next/router'
import { MdxFile } from "nextra"
import { Link } from "nextra-theme-docs"
import { getPagesUnderRoute } from "nextra/context"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
} from "@/components/ui/form";
import { websiteConfig } from '@/lib/data';
import { dayjsTz } from '@/lib/utils';
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter } from 'next/router';
import { MdxFile } from "nextra";
import { Link } from "nextra-theme-docs";
import { getPagesUnderRoute } from "nextra/context";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import Picture from "./picture";
import { Button } from "./ui/button";
import { Input } from "./ui/input";

// Header and Index derived from https://github.com/vercel/turbo/blob/66196a70d02cddc8899ed1423684b1f716aa310e/docs/pages/blog.mdx
export function BlogHeader() {
return (
<div className="max-w-screen-lg mx-auto pt-4 pb-8 mb-16 border-b border-gray-400 border-opacity-20">
<div className="max-w-screen-lg mx-auto pt-4 pb-8 border-b border-gray-400 border-opacity-20">
<h1>
<span className="font-bold leading-tight lg:text-5xl">Breadcrumbs</span>
</h1>
Expand All @@ -44,37 +46,67 @@ export function BlogHeader() {
export function BlogIndex() {
const { locale = websiteConfig.default_locale } = useRouter()

return getPagesUnderRoute("/blog").map((page) => {
const frontMatter = (page as MdxFile).frontMatter
const { date, timezone } = frontMatter || {}
const items = getPagesUnderRoute("/blog").map((page) => {
const frontMatter = (page as MdxFile).frontMatter || {}
if (frontMatter.hidden) {
return null
}

const { date, timezone } = frontMatter
const dateObj = date && timezone && dayjsTz(date, timezone).toDate()

let titleImageComponent;
if (frontMatter.title_image) {
const titleImage = allImages[frontMatter.title_image];
if (!titleImage) {
throw new Error(`No image found for title_image: ${frontMatter.title_image}`);
}
const titleImageAttribution = frontMatter.title_image_attribution;
if (!titleImageAttribution) {
throw new Error(`No attribution found for title_image: ${frontMatter.title_image}`);
}

titleImageComponent = (
<Picture
image={titleImage}
alt={titleImageAttribution}
wrapperClassName='ml-4 block'
imgClassName='max-h-40 max-w-40 w-40 h-auto object-contain'
/>
)
}

return (
<div key={page.route} className="mb-10">
<Link href={page.route} style={{ color: "inherit", textDecoration: "none" }} className="block font-semibold mt-8 text-2xl">
{page.meta?.title || frontMatter?.title || page.name}
</Link>
<p className="opacity-80" style={{ marginTop: ".5rem" }}>
{frontMatter?.description}{" "}
<span className="inline-block">
<Link href={page.route}>{"Read more →"}</Link>
</span>
</p>
{dateObj ? (
<p className="opacity-50 text-sm">
{/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */}
<time dateTime={dateObj.toISOString()} suppressHydrationWarning>
{dateObj.toLocaleDateString(locale, {
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</time>
<div key={page.route} className="flex items-center">
<div>
<Link href={page.route} style={{ color: "inherit", textDecoration: "none" }} className="block font-semibold text-2xl">
{page.meta?.title || frontMatter.title || page.name}
</Link>
<p className="opacity-80" style={{ marginTop: ".5rem" }}>
{frontMatter.description}{" "}
<span className="inline-block">
<Link href={page.route}>{"Read more →"}</Link>
</span>
</p>
) : null}
{dateObj ? (
<p className="opacity-50 text-sm">
{/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */}
<time dateTime={dateObj.toISOString()} suppressHydrationWarning>
{dateObj.toLocaleDateString(locale, {
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</time>
</p>
) : null}
</div>
<div className="hidden md:block">{titleImageComponent}</div>
</div>
);
});
})

return <div className="grid gap-y-10 my-16">{items}</div>
}

const subscribeFormSchema = z.object({
Expand Down
10 changes: 6 additions & 4 deletions components/picture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import { getImageBlurSvg } from "@/lib/image-blur-svg";
export default function Picture({
image,
alt,
className = "",
wrapperClassName = "",
imgClassName = "",
style = {},
}: {
image: WATcloudStaticImage;
alt: string;
className?: string;
wrapperClassName?: string;
imgClassName?: string;
style?: React.CSSProperties;
}) {
// The blur logic is derived from https://github.com/vercel/next.js/blob/98be3ba23ea65ac5b581999d79a1093f147b46f0/packages/next/src/shared/lib/get-img-props.ts#L626
Expand Down Expand Up @@ -46,7 +48,7 @@ export default function Picture({
};

return (
<picture>
<picture className={wrapperClassName}>
<source srcSet={image.avif.src} type="image/avif" />
<source srcSet={image.webp.src} type="image/webp" />
<img
Expand All @@ -56,7 +58,7 @@ export default function Picture({
height={image.avif.height}
decoding="async"
loading="lazy"
className={className}
className={imgClassName}
style={{...style, ...placeholderStyle}}
/>
</picture>
Expand Down
Loading

0 comments on commit 19742ee

Please sign in to comment.