initial commit
This commit is contained in:
75
components/utils/Footer.tsx
Normal file
75
components/utils/Footer.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { RSSFeedLink } from "@/consts/consts";
|
||||
import { Config } from "@/data/config";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import CopyToClipboard from "react-copy-to-clipboard";
|
||||
import { FaCheck, FaCopy } from "react-icons/fa";
|
||||
import { IoLogoRss } from "react-icons/io5";
|
||||
import { Button } from "../ui/button";
|
||||
import { Input } from "../ui/input";
|
||||
import { Separator } from "../ui/separator";
|
||||
|
||||
export const Footer = () => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
return (
|
||||
<Dialog
|
||||
onOpenChange={() => {
|
||||
setIsCopied(false);
|
||||
}}
|
||||
>
|
||||
<footer className="my-5 flex flex-col justify-center py-2 text-sm">
|
||||
<div className="mx-auto px-3 text-center font-bold">{`COPYRIGHT © ${
|
||||
Config.YearStart
|
||||
}-${new Date().getFullYear()} ${Config.AuthorName} ALL RIGHTS RESERVED`}</div>
|
||||
<div className="my-3 flex flex-wrap justify-center space-x-3 text-center text-gray-500 underline dark:text-gray-400">
|
||||
<Link href="/sponsor" title="Sponsor me for my works.">
|
||||
{"Sponsor"}
|
||||
</Link>
|
||||
<Link href="/friends" title="My friend links.">
|
||||
{"Friends"}
|
||||
</Link>
|
||||
<DialogTrigger asChild>
|
||||
<button title="Subscribe the RSS Feed.">{"Feed"}</button>
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex">
|
||||
<IoLogoRss className="mr-2 my-auto" />
|
||||
{"RSS Feed"}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div>
|
||||
<div className="w-full text-sm my-2">
|
||||
<div>
|
||||
<b>NOTE: </b>Some RSS Feed Reader may has deficient in rendering SVG formulations, graphs. Such as the
|
||||
Inoreader, Feedly. If it happens, please read the origin web page for better experience.
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="w-full flex my-3">
|
||||
<Input defaultValue={RSSFeedLink} readOnly />
|
||||
<CopyToClipboard
|
||||
onCopy={() => {
|
||||
setIsCopied(true);
|
||||
}}
|
||||
text={RSSFeedLink}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
className={`ml-3 my-auto ${isCopied && "bg-green-500 hover:bg-green-500"}`}
|
||||
>
|
||||
<span className="sr-only">{"Copy"}</span>
|
||||
{isCopied ? <FaCheck className="h-4 w-4" /> : <FaCopy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</footer>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
103
components/utils/NavBar.tsx
Normal file
103
components/utils/NavBar.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||
import { Config } from "@/data/config";
|
||||
import { fontFzxbs } from "@/styles/font";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useTheme } from "next-themes";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { MdMenu, MdOutlineDarkMode, MdOutlineLightMode } from "react-icons/md";
|
||||
|
||||
const MenuItems = [
|
||||
{
|
||||
title: "HOME",
|
||||
href: "/",
|
||||
},
|
||||
{
|
||||
title: "TAGS",
|
||||
href: "/tags",
|
||||
},
|
||||
{
|
||||
title: "POSTS",
|
||||
href: "/posts",
|
||||
},
|
||||
{
|
||||
title: "ABOUT",
|
||||
href: "/about",
|
||||
},
|
||||
];
|
||||
|
||||
export const NavBar = () => {
|
||||
const [isSideNavOpen, setIsSideNavOpen] = useState(false);
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const handleSwitchTheme = () => {
|
||||
theme === "light" ? setTheme("dark") : setTheme("light");
|
||||
setIsSideNavOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={isSideNavOpen} onOpenChange={(open) => setIsSideNavOpen(open)}>
|
||||
<nav className="responsive-width sticky top-0 z-50 flex justify-between bg-inherit py-3 backdrop-blur">
|
||||
<Link href="/" className="cursor-pointer">
|
||||
<h1
|
||||
className={`${fontFzxbs.className} my-auto border-b-4 border-b-black text-2xl font-bold dark:border-b-white`}
|
||||
>
|
||||
{Config.SiteTitle}
|
||||
</h1>
|
||||
</Link>
|
||||
<div className="my-auto hidden sm:flex">
|
||||
{MenuItems.map((menuItem) => (
|
||||
<Link href={menuItem.href} key={nanoid()} className="nav-link mx-2 my-auto px-2">
|
||||
{menuItem.title}
|
||||
</Link>
|
||||
))}
|
||||
<div
|
||||
title={theme === "light" ? "Switch to dark mode" : "Switch to light mode"}
|
||||
className="cursor-pointer mx-2 rounded-full p-1 text-3xl text-black hover:bg-gray-200 dark:text-gray-50 dark:hover:bg-gray-800"
|
||||
onClick={handleSwitchTheme}
|
||||
>
|
||||
{theme === "light" ? <MdOutlineDarkMode /> : <MdOutlineLightMode />}
|
||||
</div>
|
||||
</div>
|
||||
<SheetTrigger title="Spread the navigation menu" className="sm:hidden">
|
||||
<MdMenu
|
||||
className="my-auto text-3xl hover:cursor-pointer"
|
||||
onClick={() => {
|
||||
setIsSideNavOpen(!isSideNavOpen);
|
||||
}}
|
||||
/>
|
||||
</SheetTrigger>
|
||||
<SheetContent className="bg:white border-none shadow-md dark:bg-black">
|
||||
<div className="my-5 flex flex-col">
|
||||
{MenuItems.map((menuItem) => (
|
||||
<Link
|
||||
href={menuItem.href}
|
||||
key={nanoid()}
|
||||
className="border-b border-dashed p-3 text-xl hover:text-sky-500"
|
||||
>
|
||||
{menuItem.title}
|
||||
</Link>
|
||||
))}
|
||||
<div
|
||||
title={theme === "light" ? "Switch to dark mode" : "Switch to light mode"}
|
||||
className="cursor-pointer m-2 rounded-full p-1 text-xl text-black dark:text-gray-50"
|
||||
onClick={handleSwitchTheme}
|
||||
>
|
||||
{theme === "light" ? (
|
||||
<div className="flex">
|
||||
{"DARK MODE"}
|
||||
<MdOutlineDarkMode className="mx-2 my-auto" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex">
|
||||
{"LIGHT MODE"}
|
||||
<MdOutlineLightMode className="mx-2 my-auto" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</nav>
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
12
components/utils/PageCover.tsx
Normal file
12
components/utils/PageCover.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export const PageCover = (props: { coverURL: string }) => {
|
||||
return (
|
||||
<div
|
||||
className="my-5 mt-0 flex w-full justify-center rounded-xl"
|
||||
style={{
|
||||
aspectRatio: "4/1",
|
||||
background: `url(${props.coverURL})`,
|
||||
backgroundSize: "cover",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
44
components/utils/PostList.tsx
Normal file
44
components/utils/PostList.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { normalizeDate } from "@/lib/date";
|
||||
import { fontSypxzs } from "@/styles/font";
|
||||
import { TPostListItem } from "@/types/post-list";
|
||||
import { nanoid } from "nanoid";
|
||||
import Link from "next/link";
|
||||
|
||||
export const PostList = (props: { data: TPostListItem[] }) => {
|
||||
return (
|
||||
<div>
|
||||
{props.data.map((postListItem, index) => (
|
||||
<div
|
||||
key={`post-list-${nanoid()}`}
|
||||
className={`${fontSypxzs.className} flex flex-col justify-center ${
|
||||
index !== props.data.length - 1 && "border-b"
|
||||
} border-dashed border-gray-400 py-3`}
|
||||
>
|
||||
<Link className="hover:text-gray-600 dark:hover:text-gray-400" href={`/blog/${postListItem.id}`}>
|
||||
<div className="flex-center flex flex-col py-2 ">
|
||||
<h3 className="mx-auto text-xl font-extrabold capitalize">{postListItem.frontMatter.title}</h3>
|
||||
{postListItem.frontMatter.subtitle && (
|
||||
<div className="mx-auto text-base font-semibold capitalize">{postListItem.frontMatter.subtitle}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-center">{normalizeDate(postListItem.frontMatter.time)}</div>
|
||||
{postListItem.frontMatter.summary && (
|
||||
<div className="flex my-1 justify-center">
|
||||
<p>{postListItem.frontMatter.summary}</p>
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
{postListItem.frontMatter.tags && (
|
||||
<div className="my-2 flex justify-center">
|
||||
{postListItem.frontMatter.tags.map((tagName) => (
|
||||
<Link href={`/tags/${tagName}`} className="tag-link mx-1 text-sm" key={`tags-${nanoid()}`}>
|
||||
{tagName}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
35
components/utils/SEO.tsx
Normal file
35
components/utils/SEO.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { RSSFeedLink } from "@/consts/consts";
|
||||
import { Config } from "@/data/config";
|
||||
import { NextSeo } from "next-seo";
|
||||
|
||||
export const SEO = (props: { title: string; description?: string | null; coverURL?: string | null }) => {
|
||||
return (
|
||||
<>
|
||||
<title>{props.title}</title>
|
||||
<link rel="alternate" type="application/rss+xml" href={RSSFeedLink} />
|
||||
<NextSeo
|
||||
title={props.title}
|
||||
description={props.description ?? undefined}
|
||||
openGraph={{
|
||||
title: props.title,
|
||||
description: props.description ?? undefined,
|
||||
images: props.coverURL
|
||||
? [
|
||||
{
|
||||
url: props.coverURL,
|
||||
width: 850,
|
||||
height: 650,
|
||||
alt: props.title,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}}
|
||||
twitter={{
|
||||
handle: `@${Config.SocialLinks.twitter}`,
|
||||
site: Config.SiteDomain,
|
||||
cardType: "summary_large_image",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
46
components/utils/SocialIcons.tsx
Normal file
46
components/utils/SocialIcons.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Config } from "@/data/config";
|
||||
import Link from "next/link";
|
||||
import { FiGithub, FiInstagram, FiMail, FiTwitter } from "react-icons/fi";
|
||||
import { TbBrandFacebook, TbBrandLinkedin, TbBrandMastodon } from "react-icons/tb";
|
||||
|
||||
export const SocialIcons = () => {
|
||||
return (
|
||||
<div className="my-5 flex justify-center space-x-4 text-2xl font-bold">
|
||||
{Config.SocialLinks.twitter && (
|
||||
<Link target="_blank" href={`https://x.com/${Config.SocialLinks.twitter}`} title="Twitter">
|
||||
<FiTwitter className="hover:text-sky-500" />
|
||||
</Link>
|
||||
)}
|
||||
{Config.SocialLinks.mastodon && (
|
||||
<Link target="_blank" href={Config.SocialLinks.mastodon} title="Mastodon">
|
||||
<TbBrandMastodon className="hover:text-purple-500" />
|
||||
</Link>
|
||||
)}
|
||||
{Config.SocialLinks.instagram && (
|
||||
<Link target="_blank" href={`https://instagram.com/${Config.SocialLinks.instagram}`} title="Instagram">
|
||||
<FiInstagram className="hover:text-orange-500" />
|
||||
</Link>
|
||||
)}
|
||||
{Config.SocialLinks.facebook && (
|
||||
<Link target="_blank" href={`https://instagram.com/${Config.SocialLinks.facebook}`} title="Instagram">
|
||||
<TbBrandFacebook className="hover:text-blue-500" />
|
||||
</Link>
|
||||
)}
|
||||
{Config.SocialLinks.linkedin && (
|
||||
<Link target="_blank" href={`https://linkedin.com/in/${Config.SocialLinks.linkedin}`} title="Instagram">
|
||||
<TbBrandLinkedin className="hover:text-blue-500" />
|
||||
</Link>
|
||||
)}
|
||||
{Config.SocialLinks.github && (
|
||||
<Link target="_blank" href={`https://github.com/${Config.SocialLinks.github}`} title="Github">
|
||||
<FiGithub className="hover:text-gray-500" />
|
||||
</Link>
|
||||
)}
|
||||
{Config.SocialLinks.email && (
|
||||
<Link target="_blank" href={`mailto:${Config.SocialLinks.email}`} title="EMail Address">
|
||||
<FiMail className="hover:text-gray-500" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user