Added blog components

This commit is contained in:
killerboss 2024-10-30 18:28:41 +01:00
parent e959e6fc3f
commit 43d161b3d1
15 changed files with 394 additions and 0 deletions

40
blog/alert.tsx Normal file
View file

@ -0,0 +1,40 @@
import Container from "@/components/blog/container";
import {cn} from "@/lib/utils";
type Props = {
preview?: boolean;
};
const Alert = ({ preview }: Props) => {
return (
<div
className={cn("border-b dark:bg-slate-800", {
"bg-neutral-800 border-neutral-800 text-white": preview,
"bg-neutral-50 border-neutral-200": !preview,
})}
>
<Container>
<div className="py-2 text-center text-sm">
{preview ? (
<>
This page is a preview.{" "}
<a
href="/api/exit-preview"
className="underline hover:text-teal-300 duration-200 transition-colors"
>
Click here
</a>{" "}
to exit preview mode.
</>
) : (
<>
Thunder Network
</>
)}
</div>
</Container>
</div>
);
};
export default Alert;

15
blog/avatar.tsx Normal file
View file

@ -0,0 +1,15 @@
type Props = {
name: string;
picture: string;
};
const Avatar = ({ name, picture }: Props) => {
return (
<div className="flex items-center">
<img src={picture} className="w-12 h-12 rounded-full mr-4" alt={name} />
<div className="text-xl font-bold">{name}</div>
</div>
);
};
export default Avatar;

9
blog/container.tsx Normal file
View file

@ -0,0 +1,9 @@
type Props = {
children?: React.ReactNode;
};
const Container = ({ children }: Props) => {
return <div className="container mx-auto px-5">{children}</div>;
};
export default Container;

36
blog/cover-image.tsx Normal file
View file

@ -0,0 +1,36 @@
import Link from "next/link";
import Image from "next/image";
import {cn} from "@/lib/utils";
type Props = {
title: string;
src: string;
slug?: string;
};
const CoverImage = ({ title, src, slug }: Props) => {
const image = (
<Image
src={src}
alt={`Cover Image for ${title}`}
className={cn("shadow-sm w-full", {
"hover:shadow-lg transition-shadow duration-200": slug,
})}
width={1300}
height={630}
/>
);
return (
<div className="sm:mx-0">
{slug ? (
<Link href={`/blog/${slug}`} aria-label={title}>
{image}
</Link>
) : (
image
)}
</div>
);
};
export default CoverImage;

12
blog/date-formatter.tsx Normal file
View file

@ -0,0 +1,12 @@
import { parseISO, format } from "date-fns";
type Props = {
dateString: string;
};
const DateFormatter = ({ dateString }: Props) => {
const date = parseISO(dateString);
return <time dateTime={dateString}>{format(date, "LLLL d, yyyy")}</time>;
};
export default DateFormatter;

32
blog/footer.tsx Normal file
View file

@ -0,0 +1,32 @@
import Container from "@/components/blog/container";
import { EXAMPLE_PATH } from "@/lib/blog/constants";
export function Footer() {
return (
<footer className="bg-neutral-50 border-t border-neutral-200 dark:bg-slate-800">
<Container>
<div className="py-28 flex flex-col lg:flex-row items-center">
<h3 className="text-4xl lg:text-[2.5rem] font-bold tracking-tighter leading-tight text-center lg:text-left mb-10 lg:mb-0 lg:pr-4 lg:w-1/2">
Statically Generated with Next.js.
</h3>
<div className="flex flex-col lg:flex-row justify-center items-center lg:pl-4 lg:w-1/2">
<a
href="https://nextjs.org/docs/app/building-your-application/routing/layouts-and-templates"
className="mx-3 bg-black hover:bg-white hover:text-black border border-black text-white font-bold py-3 px-12 lg:px-8 duration-200 transition-colors mb-6 lg:mb-0"
>
Read Documentation
</a>
<a
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
className="mx-3 font-bold hover:underline"
>
View on GitHub
</a>
</div>
</div>
</Container>
</footer>
);
}
export default Footer;

14
blog/header.tsx Normal file
View file

@ -0,0 +1,14 @@
import Link from "next/link";
const Header = () => {
return (
<h2 className="text-2xl md:text-4xl font-bold tracking-tight md:tracking-tighter leading-tight mb-20 mt-8 flex items-center">
<Link href="/" className="hover:underline">
Blog
</Link>
.
</h2>
);
};
export default Header;

47
blog/hero-post.tsx Normal file
View file

@ -0,0 +1,47 @@
import Avatar from "@/components/blog/avatar";
import CoverImage from "@/components/blog/cover-image";
import { type Author } from "@/interfaces/author";
import Link from "next/link";
import DateFormatter from "./date-formatter";
type Props = {
title: string;
coverImage: string;
date: string;
excerpt: string;
author: Author;
slug: string;
};
export function HeroPost({
title,
coverImage,
date,
excerpt,
author,
slug,
}: Props) {
return (
<section>
<div className="mb-8 md:mb-16">
<CoverImage title={title} src={coverImage} slug={slug} />
</div>
<div className="md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 mb-20 md:mb-28">
<div>
<h3 className="mb-4 text-4xl lg:text-5xl leading-tight">
<Link href={`/blog/${slug}`} className="hover:underline">
{title}
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<DateFormatter dateString={date} />
</div>
</div>
<div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
<Avatar name={author.name} picture={author.picture} />
</div>
</div>
</section>
);
}

13
blog/intro.tsx Normal file
View file

@ -0,0 +1,13 @@
export function Intro() {
return (
<section className="flex-col md:flex-row flex items-center md:justify-between mt-16 mb-16 md:mb-12">
<h1 className="text-5xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
Blog.
</h1>
<h4 className="text-center md:text-left text-lg mt-5 md:pl-8">
Thunder Network
</h4>
</section>
);
}

29
blog/more-stories.tsx Normal file
View file

@ -0,0 +1,29 @@
import { Post } from "@/interfaces/post";
import { PostPreview } from "./post-preview";
type Props = {
posts: Post[];
};
export function MoreStories({ posts }: Props) {
return (
<section>
<h2 className="mb-8 text-5xl md:text-7xl font-bold tracking-tighter leading-tight">
More Stories
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32">
{posts.map((post) => (
<PostPreview
key={post.slug}
title={post.title}
coverImage={post.coverImage}
date={post.date}
author={post.author}
slug={post.slug}
excerpt={post.excerpt}
/>
))}
</div>
</section>
);
}

34
blog/post-header.tsx Normal file
View file

@ -0,0 +1,34 @@
import Avatar from "./avatar";
import CoverImage from "./cover-image";
import DateFormatter from "./date-formatter";
import { PostTitle } from "@/components/blog/post-title";
import { type Author } from "@/interfaces/author";
type Props = {
title: string;
coverImage: string;
date: string;
author: Author;
};
export function PostHeader({ title, coverImage, date, author }: Props) {
return (
<>
<PostTitle>{title}</PostTitle>
<div className="hidden md:block md:mb-12">
<Avatar name={author.name} picture={author.picture} />
</div>
<div className="mb-8 md:mb-16 sm:mx-0">
<CoverImage title={title} src={coverImage} />
</div>
<div className="max-w-2xl mx-auto">
<div className="block md:hidden mb-6">
<Avatar name={author.name} picture={author.picture} />
</div>
<div className="mb-6 text-lg">
<DateFormatter dateString={date} />
</div>
</div>
</>
);
}

41
blog/post-preview.tsx Normal file
View file

@ -0,0 +1,41 @@
import { type Author } from "@/interfaces/author";
import Link from "next/link";
import Avatar from "./avatar";
import CoverImage from "./cover-image";
import DateFormatter from "./date-formatter";
type Props = {
title: string;
coverImage: string;
date: string;
excerpt: string;
author: Author;
slug: string;
};
export function PostPreview({
title,
coverImage,
date,
excerpt,
author,
slug,
}: Props) {
return (
<div>
<div className="mb-5">
<CoverImage slug={slug} title={title} src={coverImage} />
</div>
<h3 className="text-3xl mb-3 leading-snug">
<Link href={`/posts/${slug}`} className="hover:underline">
{title}
</Link>
</h3>
<div className="text-lg mb-4">
<DateFormatter dateString={date} />
</div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
<Avatar name={author.name} picture={author.picture} />
</div>
);
}

13
blog/post-title.tsx Normal file
View file

@ -0,0 +1,13 @@
import { ReactNode } from "react";
type Props = {
children?: ReactNode;
};
export function PostTitle({ children }: Props) {
return (
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-tight md:leading-none mb-12 text-center md:text-left">
{children}
</h1>
);
}

View file

@ -0,0 +1,3 @@
export function SectionSeparator() {
return <hr className="border-neutral-200 mt-28 mb-24" />;
}

56
blog/switch.module.css Normal file
View file

@ -0,0 +1,56 @@
.switch {
all: unset;
position: absolute;
right: 20px;
top: 70px;
display: inline-block;
color: currentColor;
border-radius: 50%;
border: 1px dashed currentColor;
cursor: pointer;
--size: 24px;
height: var(--size);
width: var(--size);
transition: all 0.3s ease-in-out 0s !important;
}
[data-mode="system"] .switch::after {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
font-weight: 600;
font-size: calc(var(--size) / 2);
display: flex;
align-items: center;
justify-content: center;
content: "A";
}
[data-mode="light"] .switch {
box-shadow: 0 0 50px 10px yellow;
background-color: yellow;
border: 1px solid orangered;
}
[data-mode="dark"] .switch {
box-shadow: calc(var(--size) / 4) calc(var(--size) / -4) calc(var(--size) / 8)
inset #fff;
border: none;
background: transparent;
animation: n linear 0.5s;
}
@keyframes n {
40% {
transform: rotate(-15deg);
}
80% {
transform: rotate(10deg);
}
0%,
100% {
transform: rotate(0deg);
}
}