Upload starter template
This commit is contained in:
43
components/BlogSeo.js
Normal file
43
components/BlogSeo.js
Normal 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
28
components/Footer.js
Normal 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
7
components/Image.js
Normal file
@ -0,0 +1,7 @@
|
||||
import Img from 'react-optimized-image'
|
||||
|
||||
const Image = ({ src, alt }) => {
|
||||
return <Img src={src} alt={alt} />
|
||||
}
|
||||
|
||||
export default Image
|
54
components/LayoutWrapper.js
Normal file
54
components/LayoutWrapper.js
Normal 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
22
components/Link.js
Normal 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
|
9
components/MDXComponents.js
Normal file
9
components/MDXComponents.js
Normal 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
78
components/MobileNav.js
Normal 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
7
components/PageTitle.js
Normal 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
29
components/SEO.js
Normal 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
|
3
components/SectionContainer.js
Normal file
3
components/SectionContainer.js
Normal 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
14
components/Tag.js
Normal 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
33
components/ThemeSwitch.js
Normal 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
|
1
components/social-icons/facebook.svg
Normal file
1
components/social-icons/facebook.svg
Normal 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 |
1
components/social-icons/github.svg
Normal file
1
components/social-icons/github.svg
Normal 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 |
37
components/social-icons/index.js
Normal file
37
components/social-icons/index.js
Normal 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
|
1
components/social-icons/linkedin.svg
Normal file
1
components/social-icons/linkedin.svg
Normal 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 |
4
components/social-icons/mail.svg
Normal file
4
components/social-icons/mail.svg
Normal 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 |
1
components/social-icons/twitter.svg
Normal file
1
components/social-icons/twitter.svg
Normal 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 |
1
components/social-icons/youtube.svg
Normal file
1
components/social-icons/youtube.svg
Normal 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 |
Reference in New Issue
Block a user