[portal.js/examples][s]: added nextjs-tailwind-mdx to examples

This commit is contained in:
olayway 2022-05-09 14:06:51 +02:00
parent 430c60978a
commit 6eaba46954
17 changed files with 9020 additions and 0 deletions

34
examples/nextjs-tailwind-mdx/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View File

@ -0,0 +1,56 @@
# Next.js + Tailwind CSS + MDX Starter Template
A starter template for Next.JS with Tailwind and MDX. Intentionally limited feature-set to keep things simple. (If you want a more comprenhensive starter template check out https://github.com/timlrx/tailwind-nextjs-starter-blog).
Includes instructions on how to rapidly customize the site.
Preview online at https://nextjs-tailwind-mdx-tau.vercel.app
## Features
Pre-configured with the following;
* Tailwind for easy styling. Booted off NextJS default tailwindcss example (https://github.com/vercel/next.js/tree/canary/packages/create-next-app)
* Markdown / MDX rendering support. All markdown/MDX in `/content/` gets auto-rendered into the site.
* Configurable e.g. site title etc (secret config in environment variables). See `Configuration` below.
* Simple theming via `components/Layout.js`. Used to provide a standard them for all pages. Customizable NavBar and Footer with configurable nav links.
* Analytics: Google analytics support following https://github.com/vercel/next.js/tree/canary/examples/with-google-analytics
* SEO: basic SEO out of the box (via https://github.com/garmeeh/next-seo)
## Usage
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
npx create-next-app --example https://github.com/datopian/nextjs-tailwind-mdx myapp
# or
yarn create next-app --example https://github.com/datopian/nextjs-tailwind-mdx myapp
```
Then run the app:
```bash
cd myapp
npm run dev
```
## Configuration
See `config` directory:
* `config/siteConfig.js` for site wide configuration especially for general theme (e.g. title) and SEO
* `config/navLinks.js` for configuration of navigation links
### How to customize the content directory location?
Open up `pages/[...slug].js` and change the `CONTENT_PATH` variable.
### Theming
We suggest the following:
* Replace the favicon in `public/favicon.ico` or use the svg favicon ...
* Add a logo: add image to `public` e.g. then open `components/layout.js` and replace footer logo link
Tweaking the theme in general: open up `components/Layout.js` and tweak away.

View File

@ -0,0 +1,37 @@
import Link from 'next/link'
import Head from 'next/head'
import { NextSeo } from 'next-seo'
import Nav from './Nav'
import siteConfig from '../config/siteConfig'
export default function Layout({ children, title='' }) {
return (
<>
<NextSeo
title={title}
/>
<Head>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚀</text></svg>" />
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<Nav />
<main>
{children}
</main>
<footer className="flex items-center justify-center w-full h-24 border-t mt-16">
<p className="flex items-center justify-center">
Created by
<a
href={siteConfig.authorUrl}
target="_blank"
rel="noopener noreferrer"
>
<img src={siteConfig.authorLogo} alt={siteConfig.author} className="ml-2 h-6 block" />
</a>
</p>
</footer>
</>
)
}

View File

@ -0,0 +1,30 @@
import { MDXRemote } from 'next-mdx-remote'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import Link from 'next/link'
const components = {
Head,
}
export default function MdxPage({ children, source, frontMatter }) {
return (
<article className="prose mx-auto p-6">
<header>
<div className="mb-6">
<h1>{frontMatter.title}</h1>
{frontMatter.author && (
<div className="-mt-6"><p className="opacity-60 pl-1">{frontMatter.author}</p></div>
)}
{frontMatter.description && (
<p className="description">{frontMatter.description}</p>
)}
</div>
</header>
<section>
<MDXRemote {...source} components={components} />
</section>
</article>
)
}

View File

@ -0,0 +1,83 @@
import { Fragment } from 'react'
import { Disclosure, Menu, Transition } from '@headlessui/react'
import { BellIcon, MenuIcon, XIcon } from '@heroicons/react/outline'
import Link from 'next/link'
import siteConfig from '../config/siteConfig.js'
import navLinks from '../config/navLinks.js'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Nav() {
return (
<Disclosure as="nav" className="shadow">
{({ open }) => (
<>
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="relative flex justify-between h-16">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button */}
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block h-6 w-6" aria-hidden="true" />
) : (
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex-shrink-0 flex items-center">
<Link href="/">
<a>{siteConfig.title}</a>
</Link>
</div>
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
{/* Current: "border-indigo-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" */}
{navLinks.map((item) => (
<Link href={item.href}>
<a
key={item.name}
href={item.href}
className={item.current ?
'border-indigo-500 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium' :
'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium'
}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
</Link>
))}
</div>
</div>
</div>
</div>
<Disclosure.Panel className="sm:hidden">
<div className="pt-2 pb-4 space-y-1">
{/* Current: "bg-indigo-50 border-indigo-500 text-indigo-700", Default: "border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700" */}
{navLinks.map((item) => (
<Link href={item.href}>
<a
key={item.name}
href={item.href}
className={item.current ?
'bg-indigo-50 border-indigo-500 text-indigo-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium' :
'border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium'
}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
</Link>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
)
}

View File

@ -0,0 +1,5 @@
const navLinks = [
{ href: '/about', name: 'About' },
]
export default navLinks

View File

@ -0,0 +1,29 @@
const siteConfig = {
title: 'Datopian Next.js Template',
tagline: 'A starter template for Next.JS with Tailwind and MDX',
description: 'Hello ...',
author: 'Datopian',
// logo image
authorLogo: 'https://playbook.datopian.com/img/datopian-dark-logotype.svg',
// url to author
authorUrl: 'https://datopian.com/',
// Google analytics key e.g. G-XXXX
analytics: '',
// optional additional nextSeo content set on each page
// see https://github.com/garmeeh/next-seo
// nextSeo: {
// openGraph: {
// images: [
// {
// url: 'https://image.url/...',
// alt: '',
// width: 1200,
// height: 627,
// type: 'image/png',
// }
// ]
// }
// }
}
module.exports = siteConfig

View File

@ -0,0 +1,19 @@
---
title: About
---
Hello, this is an about page.
Here is a footnote.[^1]
Here is an ndash --.
[^1]: footnote no 1.
## Table of contents
## Heading 2
### Heading 2.1
## Heading 3

View File

@ -0,0 +1,17 @@
import siteConfig from '../config/siteConfig.js'
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
export const pageview = (url) => {
window.gtag('config', siteConfig.analytics, {
page_path: url,
})
}
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
export const event = ({ action, category, label, value }) => {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
})
}

View File

@ -0,0 +1,33 @@
import matter from 'gray-matter'
import gfm from 'remark-gfm'
import toc from 'remark-toc'
import slug from 'remark-slug'
import smartypants from '@silvenon/remark-smartypants'
import { serialize } from 'next-mdx-remote/serialize'
/**
* Parse a markdown or MDX file to an MDX source form + front matter data
*
* @source: the contents of a markdown or mdx file
* @returns: { mdxSource: mdxSource, frontMatter: object }
*/
const parse = async function(source) {
const { content, data } = matter(source)
const mdxSource = await serialize(content, {
// Optionally pass remark/rehype plugins
mdxOptions: {
remarkPlugins: [gfm, toc, slug, smartypants],
rehypePlugins: [],
},
scope: data,
})
return {
mdxSource: mdxSource,
frontMatter: data
}
}
export default parse

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"export": "next export",
"start": "next start"
},
"dependencies": {
"@headlessui/react": "^1.4.1",
"@heroicons/react": "^1.0.4",
"@silvenon/remark-smartypants": "^1.0.0",
"@tailwindcss/typography": "^0.5.2",
"gray-matter": "^4.0.3",
"next": "^12.1.0",
"next-mdx-remote": "^3.0.5",
"next-seo": "^4.28.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"remark-gfm": "^3.0.0",
"remark-slug": "^7.0.0",
"remark-toc": "^8.0.0"
},
"devDependencies": {
"autoprefixer": "^10.4.4",
"postcss": "^8.4.12",
"tailwindcss": "^3.0.23"
}
}

View File

@ -0,0 +1,68 @@
import fs from 'fs'
import path from 'path'
import parse from '../lib/mdx.js'
import MdxPage from '../components/MDX'
export default function Page({ source, frontMatter, title }) {
return (
<MdxPage source={source} frontMatter={frontMatter} />
)
}
const CONTENT_PATH = path.join(process.cwd(), 'content/')
export const getStaticProps = async ({ params }) => {
const mdxPath = path.join(CONTENT_PATH, `${params.slug.join('/')}.mdx`)
const postFilePath = fs.existsSync(mdxPath) ? mdxPath : mdxPath.slice(0, -1)
const source = fs.readFileSync(postFilePath)
const { mdxSource, frontMatter } = await parse(source)
return {
props: {
source: mdxSource,
frontMatter: frontMatter,
title: frontMatter.title || ''
},
}
}
export const getStaticPaths = async () => {
const walkSync = (dir, filelist = []) => {
fs.readdirSync(dir).forEach(file => {
const tpath = fs.statSync(path.join(dir, file))
if (tpath.isDirectory()) {
filelist = walkSync(path.join(dir, file), filelist)
} else {
filelist = filelist.concat(path.join(dir, file))
}
})
return filelist
}
// postFilePaths is the list of all mdx files inside the CONTENT_PATH directory
var filePaths = walkSync(CONTENT_PATH)
.map((file) => { return file.slice(CONTENT_PATH.length) })
// Only include md(x) files
.filter((path) => /\.mdx?$/.test(path))
var filePaths = filePaths
// Remove file extensions for page paths
.map((path) => path.replace(/\.mdx?$/, ''))
// Map the path into the static paths object required by Next.js
const paths = filePaths.map((slug) => {
// /demo => [demo]
const parts = slug.split('/')
return { params: { slug: parts } }
})
return {
paths,
fallback: false,
}
}

View File

@ -0,0 +1,65 @@
import { useEffect } from 'react'
import Script from 'next/script'
import { useRouter } from 'next/router'
import { DefaultSeo } from 'next-seo'
import 'tailwindcss/tailwind.css'
import siteConfig from '../config/siteConfig.js'
import Layout from '../components/Layout'
import * as gtag from '../lib/gtag'
function MyApp({ Component, pageProps }) {
// Google Analytics
if (siteConfig.analytics) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url) => {
gtag.pageview(url)
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
}
// end Google Analytics
return (
<>
<DefaultSeo
titleTemplate={'%s | ' + siteConfig.title}
defaultTitle={siteConfig.title}
description={siteConfig.description}
{...siteConfig.nextSeo}
/>
{/* Global Site Tag (gtag.js) - Google Analytics */}
{siteConfig.analytics &&
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${siteConfig.analytics}`}
/>
}
{siteConfig.analytics &&
<Script
id="gtag-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${siteConfig.analytics}', {
page_path: window.location.pathname,
});
`,
}}
/>
}
<Layout title={pageProps.title}>
<Component {...pageProps} />
</Layout>
</>
)
}
export default MyApp

View File

@ -0,0 +1,20 @@
import Head from 'next/head'
import siteConfig from '../config/siteConfig'
export default function Home() {
return (
<>
<div className="text-center">
<h1 className="text-6xl font-bold mt-24 mb-8">
<a className="text-blue-600" href="https://github.com/datopian/nextjs-tailwind-mdx">
{siteConfig.title}
</a>
</h1>
<h2 className="text-4xl">
{siteConfig.tagline}
</h2>
</div>
</>
)
}

View File

@ -0,0 +1,8 @@
// If you want to use other PostCSS plugins, see the following:
// https://tailwindcss.com/docs/using-with-preprocessors
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -0,0 +1,9 @@
module.exports = {
content: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/typography')
],
}