Added blog components
This commit is contained in:
parent
e959e6fc3f
commit
43d161b3d1
15 changed files with 394 additions and 0 deletions
40
blog/alert.tsx
Normal file
40
blog/alert.tsx
Normal 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
15
blog/avatar.tsx
Normal 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
9
blog/container.tsx
Normal 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
36
blog/cover-image.tsx
Normal 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
12
blog/date-formatter.tsx
Normal 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
32
blog/footer.tsx
Normal 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
14
blog/header.tsx
Normal 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
47
blog/hero-post.tsx
Normal 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
13
blog/intro.tsx
Normal 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
29
blog/more-stories.tsx
Normal 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
34
blog/post-header.tsx
Normal 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
41
blog/post-preview.tsx
Normal 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
13
blog/post-title.tsx
Normal 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>
|
||||
);
|
||||
}
|
3
blog/section-separator.tsx
Normal file
3
blog/section-separator.tsx
Normal 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
56
blog/switch.module.css
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue