Upload starter template

This commit is contained in:
Timothy Lin
2021-01-09 17:50:45 +08:00
parent e332766d0f
commit 9a6f4efbb8
72 changed files with 11703 additions and 0 deletions

43
components/BlogSeo.js Normal file
View File

@ -0,0 +1,43 @@
import { NextSeo, ArticleJsonLd } from 'next-seo'
import siteMetadata from '@/data/siteMetadata'
const BlogSeo = ({ title, summary, date, url, image }) => {
const publishedAt = new Date(date).toISOString()
const featuredImage = {
url: `${siteMetadata.url}${image}`,
alt: title,
}
return (
<>
<NextSeo
title={`${title} ${siteMetadata.title}`}
description={summary}
canonical={url}
openGraph={{
type: 'article',
article: {
publishedTime: publishedAt,
},
url,
title,
description: summary,
images: [featuredImage],
}}
/>
<ArticleJsonLd
authorName={siteMetadata.author}
dateModified={publishedAt}
datePublished={publishedAt}
description={summary}
images={[featuredImage]}
publisherLogo="/static/favicons/android-chrome-192x192.png"
publisherName={siteMetadata.author}
title={title}
url={url}
/>
</>
)
}
export default BlogSeo

28
components/Footer.js Normal file
View File

@ -0,0 +1,28 @@
import Link from './Link'
import siteMetadata from '@/data/siteMetadata'
import SocialIcon from '@/components/social-icons'
export default function Footer() {
return (
<footer className="flex flex-col items-center mt-16">
<div className="flex space-x-4 mb-3">
<SocialIcon kind="mail" href={`mailto:${siteMetadata.email}`} size="6" />
<SocialIcon kind="github" href={siteMetadata.github} size="6" />
<SocialIcon kind="facebook" href={siteMetadata.facebook} size="6" />
<SocialIcon kind="youtube" href={siteMetadata.youtube} size="6" />
<SocialIcon kind="linkedin" href={siteMetadata.linkedin} size="6" />
<SocialIcon kind="twitter" href={siteMetadata.twitter} size="6" />
</div>
<div className="flex space-x-2 mb-2 text-sm text-gray-500 dark:text-gray-400">
<div>{siteMetadata.author}</div>
<div>{``}</div>
<div>{`© ${new Date().getFullYear()}`}</div>
<div>{``}</div>
<Link href="/">{siteMetadata.title}</Link>
</div>
<div className="text-sm text-gray-500 dark:text-gray-400 mb-8">
<Link href="/">Tailwind Nextjs Theme</Link>
</div>
</footer>
)
}

7
components/Image.js Normal file
View File

@ -0,0 +1,7 @@
import Img from 'react-optimized-image'
const Image = ({ src, alt }) => {
return <Img src={src} alt={alt} />
}
export default Image

View File

@ -0,0 +1,54 @@
import siteMetadata from '@/data/siteMetadata'
import headerNavLinks from '@/data/headerNavLinks'
import Logo from '@/data/logo.svg'
import Link from './Link'
import SectionContainer from './SectionContainer'
import Footer from './Footer'
import MobileNav from './MobileNav'
import ThemeSwitch from './ThemeSwitch'
const LayoutWrapper = ({ children }) => {
return (
<SectionContainer>
<div className="flex flex-col h-screen justify-between">
<header className="flex justify-between items-center py-10">
<div>
<Link href="/" aria-label="Tailwind CSS Blog">
<div className="flex justify-between items-center">
<div className="mr-3">
<Logo />
</div>
{typeof siteMetadata.headerTitle === 'string' ? (
<div className="hidden sm:block h-6 text-2xl font-semibold">
{siteMetadata.headerTitle}
</div>
) : (
siteMetadata.headerTitle
)}
</div>
</Link>
</div>
<div className="flex items-center text-base leading-5">
<div className="hidden sm:block">
{headerNavLinks.map((link) => (
<Link
key={link.title}
href={link.href}
className="p-1 sm:p-4 font-medium text-gray-900 dark:text-gray-100"
>
{link.title}
</Link>
))}
</div>
<ThemeSwitch />
<MobileNav />
</div>
</header>
<main className="mb-auto">{children}</main>
<Footer />
</div>
</SectionContainer>
)
}
export default LayoutWrapper

22
components/Link.js Normal file
View File

@ -0,0 +1,22 @@
import Link from 'next/link'
const CustomLink = ({ href, ...rest }) => {
const isInternalLink = href && href.startsWith('/')
const isAnchorLink = href && href.startsWith('#')
if (isInternalLink) {
return (
<Link href={href}>
<a {...rest} />
</Link>
)
}
if (isAnchorLink) {
return <a {...rest} />
}
return <a target="_blank" rel="noopener noreferrer" href={href} {...rest} />
}
export default CustomLink

View File

@ -0,0 +1,9 @@
import Image from 'next/image'
import CustomLink from './Link'
const MDXComponents = {
Image,
a: CustomLink,
}
export default MDXComponents

78
components/MobileNav.js Normal file
View File

@ -0,0 +1,78 @@
import { useState } from 'react'
import Link from './Link'
import headerNavLinks from '@/data/headerNavLinks'
const MobileNav = () => {
const [navShow, setNavShow] = useState(false)
const onToggleNav = () => {
setNavShow((status) => {
if (status) {
document.body.style.overflow = 'auto'
} else {
// Prevent scrolling
document.body.style.overflow = 'hidden'
}
return !status
})
}
return (
<div className="sm:hidden">
<button
type="button"
className="rounded ml-1 mr-1 h-8 w-8 focus:outline-none"
aria-label="Toggle Menu"
aria-hidden={!navShow}
onClick={onToggleNav}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="text-gray-900 dark:text-gray-100"
>
{navShow ? (
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
) : (
<path
fillRule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clipRule="evenodd"
/>
)}
</svg>
</button>
<div
className={`fixed w-full h-full top-24 right-0 bg-gray-200 dark:bg-gray-800 opacity-95 z-10 transform ease-in-out duration-300 ${
navShow ? 'translate-x-0' : 'translate-x-full'
}`}
>
<button
type="button"
className="w-full h-full fixed cursor-auto focus:outline-none"
onClick={onToggleNav}
></button>
<nav className="h-full mt-8 fixed">
{headerNavLinks.map((link) => (
<div key={link.title} className="py-4 px-12">
<Link
href={link.href}
className="text-2xl font-bold tracking-widest text-gray-900 dark:text-gray-100"
onClick={onToggleNav}
>
{link.title}
</Link>
</div>
))}
</nav>
</div>
</div>
)
}
export default MobileNav

7
components/PageTitle.js Normal file
View File

@ -0,0 +1,7 @@
export default function PageTitle({ children }) {
return (
<h1 className="text-3xl leading-9 font-extrabold text-gray-900 dark:text-gray-100 tracking-tight sm:text-4xl sm:leading-10 md:text-5xl md:leading-14">
{children}
</h1>
)
}

29
components/SEO.js Normal file
View File

@ -0,0 +1,29 @@
import siteMetadata from '@/data/siteMetadata'
const SEO = {
title: siteMetadata.title,
description: siteMetadata.description,
canonical: siteMetadata.siteUrl,
openGraph: {
type: 'website',
locale: siteMetadata.language,
url: siteMetadata.siteUrl,
title: siteMetadata.title,
description: siteMetadata.description,
images: [
{
url: siteMetadata.image,
alt: siteMetadata.title,
width: 1280,
height: 720,
},
],
},
twitter: {
handle: siteMetadata.twitter,
site: siteMetadata.twitter,
cardType: 'summary_large_image',
},
}
export default SEO

View File

@ -0,0 +1,3 @@
export default function SectionContainer({ children }) {
return <div className="max-w-3xl mx-auto px-4 sm:px-6 xl:max-w-5xl xl:px-0">{children}</div>
}

14
components/Tag.js Normal file
View File

@ -0,0 +1,14 @@
import Link from 'next/link'
import kebabCase from 'just-kebab-case'
const Tag = ({ text }) => {
return (
<Link href={`/tags/${kebabCase(text)}`}>
<a className="uppercase text-sm font-medium text-blue-500 hover:text-blue-600 dark:hover:text-blue-400">
{text.split(' ').join('-')}
</a>
</Link>
)
}
export default Tag

33
components/ThemeSwitch.js Normal file
View File

@ -0,0 +1,33 @@
import { useTheme } from 'next-themes'
const ThemeSwitch = () => {
const { theme, setTheme } = useTheme()
return (
<button
aria-label="Toggle Dark Mode"
type="button"
className="rounded ml-1 mr-1 sm:ml-4 p-1 h-8 w-8"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="text-gray-900 dark:text-gray-100"
>
{theme === 'dark' ? (
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
) : (
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
)}
</svg>
</button>
)
}
export default ThemeSwitch

View File

@ -0,0 +1 @@
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Facebook icon</title><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>

After

Width:  |  Height:  |  Size: 403 B

View File

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub icon</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>

After

Width:  |  Height:  |  Size: 827 B

View File

@ -0,0 +1,37 @@
import Mail from './mail.svg'
import Github from './github.svg'
import Facebook from './facebook.svg'
import Youtube from './youtube.svg'
import Linkedin from './linkedin.svg'
import Twitter from './twitter.svg'
// Icons taken from: https://simpleicons.org/
const components = {
mail: Mail,
github: Github,
facebook: Facebook,
youtube: Youtube,
linkedin: Linkedin,
twitter: Twitter,
}
const SocialIcon = ({ kind, href, size = 8 }) => {
const SocialSvg = components[kind]
return (
<a
className="text-sm text-gray-500 hover:text-gray-600 transition"
target="_blank"
rel="noopener noreferrer"
href={href}
>
<span className="sr-only">{kind}</span>
<SocialSvg
className={`fill-current text-gray-700 dark:text-gray-200 hover:text-blue-500 dark:hover:text-blue-400 h-${size} w-${size}`}
/>
</a>
)
}
export default SocialIcon

View File

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>LinkedIn icon</title><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>

After

Width:  |  Height:  |  Size: 615 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg>

After

Width:  |  Height:  |  Size: 224 B

View File

@ -0,0 +1 @@
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Twitter icon</title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>

After

Width:  |  Height:  |  Size: 607 B

View File

@ -0,0 +1 @@
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>YouTube icon</title><path d="M23.499 6.203a3.008 3.008 0 00-2.089-2.089c-1.87-.501-9.4-.501-9.4-.501s-7.509-.01-9.399.501a3.008 3.008 0 00-2.088 2.09A31.258 31.26 0 000 12.01a31.258 31.26 0 00.523 5.785 3.008 3.008 0 002.088 2.089c1.869.502 9.4.502 9.4.502s7.508 0 9.399-.502a3.008 3.008 0 002.089-2.09 31.258 31.26 0 00.5-5.784 31.258 31.26 0 00-.5-5.808zm-13.891 9.4V8.407l6.266 3.604z"/></svg>

After

Width:  |  Height:  |  Size: 474 B