diff --git a/website/_redirects b/website/_redirects new file mode 100644 index 000000000..0f79d7d79 --- /dev/null +++ b/website/_redirects @@ -0,0 +1,2 @@ +/ https://tsdx.io 301! +/* https://tsdx.io 301! \ No newline at end of file diff --git a/website2/.babelrc b/website2/.babelrc new file mode 100644 index 000000000..357e43654 --- /dev/null +++ b/website2/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["next/babel"], + "plugins": ["babel-plugin-macros", "./.nextra/babel-plugin-nextjs-mdx-patch"] +} \ No newline at end of file diff --git a/website2/.gitignore b/website2/.gitignore new file mode 100644 index 000000000..c3ee72b81 --- /dev/null +++ b/website2/.gitignore @@ -0,0 +1,4 @@ +node_modules +.next +.DS_Store +yarn-error.log \ No newline at end of file diff --git a/website2/.nextra/arrow-right.js b/website2/.nextra/arrow-right.js new file mode 100644 index 000000000..50c8163f8 --- /dev/null +++ b/website2/.nextra/arrow-right.js @@ -0,0 +1,19 @@ +export default ({ width = 24, height = 24, ...props }) => { + return ( + + + + ) +} diff --git a/website2/.nextra/babel-plugin-nextjs-mdx-patch.js b/website2/.nextra/babel-plugin-nextjs-mdx-patch.js new file mode 100644 index 000000000..c7bfec273 --- /dev/null +++ b/website2/.nextra/babel-plugin-nextjs-mdx-patch.js @@ -0,0 +1,34 @@ +/** + * Currently it's not possible to export data fetching functions from MDX pages + * because MDX includes them in `layoutProps`, and Next.js removes them at some + * point, causing a `ReferenceError`. + * + * https://github.com/mdx-js/mdx/issues/742#issuecomment-612652071 + * + * This plugin can be removed once MDX removes `layoutProps`, at least that + * seems to be the current plan. + */ + +// https://nextjs.org/docs/basic-features/data-fetching +const DATA_FETCH_FNS = ['getStaticPaths', 'getStaticProps', 'getServerProps'] + +module.exports = () => { + return { + visitor: { + ObjectProperty(path) { + if ( + DATA_FETCH_FNS.includes(path.node.value.name) && + path.findParent( + (path) => + path.isVariableDeclarator() && + path.node.id.name === 'layoutProps', + ) + ) { + path.remove() + } + }, + }, + } +} + +// https://github.com/vercel/next.js/issues/12053#issuecomment-622939046 diff --git a/website2/.nextra/config.js b/website2/.nextra/config.js new file mode 100644 index 000000000..0b055a4c5 --- /dev/null +++ b/website2/.nextra/config.js @@ -0,0 +1,11 @@ +import userConfig from '../nextra.config'; + +const defaultConfig = { + nextLinks: true, + prevLinks: true, + search: true, +}; + +export default () => { + return { ...defaultConfig, ...userConfig }; +}; diff --git a/website2/.nextra/directories.js b/website2/.nextra/directories.js new file mode 100644 index 000000000..4d3bf8740 --- /dev/null +++ b/website2/.nextra/directories.js @@ -0,0 +1,96 @@ +import preval from 'preval.macro' +import title from 'title' + +const excludes = ['/_app.js', '/_document.js', '/_error.js'] + +// watch all meta files +const meta = {} +function importAll(r) { + return r.keys().forEach(key => { + meta[key.slice(1)] = r(key) + }) +} +importAll(require.context('../pages/', true, /meta\.json$/)) + +// use macro to load the file list +const items = preval` + const { readdirSync, readFileSync } = require('fs') + const { resolve, join } = require('path') + const extension = /\.(mdx?|jsx?)$/ + + function getFiles(dir, route) { + const files = readdirSync(dir, { withFileTypes: true }) + + // go through the directory + const items = files + .map(f => { + const filePath = resolve(dir, f.name) + const fileRoute = join(route, f.name.replace(extension, '').replace(/^index$/, '')) + + if (f.isDirectory()) { + const children = getFiles(filePath, fileRoute) + if (!children.length) return null + return { name: f.name, children, route: fileRoute } + } else if (f.name === 'meta.json') { + return null + } else if (extension.test(f.name)) { + return { name: f.name.replace(extension, ''), route: fileRoute } + } + }) + .map(item => { + if (!item) return + return { ...item, metaPath: join(route, 'meta.json') } + }) + .filter(Boolean) + + return items + } + module.exports = getFiles(join(process.cwd(), 'pages'), '/') +` + +const attachPageConfig = function (items) { + let folderMeta = null + let fnames = null + + return items + .filter(item => !excludes.includes(item.name)) + .map(item => { + const { metaPath, ...rest } = item + folderMeta = meta[metaPath] + if (folderMeta) { + fnames = Object.keys(folderMeta) + } + + const pageConfig = folderMeta?.[item.name] + + if (rest.children) rest.children = attachPageConfig(rest.children) + + if (pageConfig) { + if (typeof pageConfig === 'string') { + return { ...rest, title: pageConfig } + } + return { ...rest, ...pageConfig } + } else { + if (folderMeta) { + return null + } + return { ...rest, title: title(item.name) } + } + }) + .filter(Boolean) + .sort((a, b) => { + if (folderMeta) { + return fnames.indexOf(a.name) - fnames.indexOf(b.name) + } + // by default, we put directories first + if (!!a.children !== !!b.children) { + return !!a.children ? -1 : 1 + } + // sort by file name + return a.name < b.name ? -1 : 1 + }) +} + +export default () => { + return attachPageConfig(items) +} diff --git a/website2/.nextra/docsearch.js b/website2/.nextra/docsearch.js new file mode 100644 index 000000000..8dd6f4757 --- /dev/null +++ b/website2/.nextra/docsearch.js @@ -0,0 +1,44 @@ +import { useRef, useEffect } from 'react' + +export default function () { + const input = useRef(null) + + useEffect(() => { + const inputs = ['input', 'select', 'button', 'textarea'] + + const down = (e) => { + if ( + document.activeElement && + inputs.indexOf(document.activeElement.tagName.toLowerCase() !== -1) + ) { + if (e.key === '/') { + e.preventDefault() + input.current?.focus() + } + } + } + + window.addEventListener('keydown', down) + return () => window.removeEventListener('keydown', down) + }, []) + + useEffect(() => { + if (window.docsearch) { + window.docsearch({ + apiKey: '247dd86c8ddbbbe6d7a2d4adf4f3a68a', + indexName: 'vercel_swr', + inputSelector: 'input#algolia-doc-search' + }) + } + }, []) + + return
+ +
+} diff --git a/website2/.nextra/github-icon.js b/website2/.nextra/github-icon.js new file mode 100644 index 000000000..46b77c994 --- /dev/null +++ b/website2/.nextra/github-icon.js @@ -0,0 +1,5 @@ +export default ({ height = 40 }) => { + return + + +} \ No newline at end of file diff --git a/website2/.nextra/layout.js b/website2/.nextra/layout.js new file mode 100644 index 000000000..18a857bd9 --- /dev/null +++ b/website2/.nextra/layout.js @@ -0,0 +1,332 @@ +import React, { + useState, + useEffect, + useMemo, + useContext, + createContext, +} from 'react'; +import { useRouter } from 'next/router'; +import Head from 'next/head'; +import Link from 'next/link'; +import slugify from '@sindresorhus/slugify'; +import 'focus-visible'; +import cn from 'classnames'; +import { SkipNavContent } from '@reach/skip-nav'; + +import Theme from './theme'; +import SSGContext from './ssg'; +import Search from './search'; +// import DocSearch from './docsearch' +import GitHubIcon from './github-icon'; +import ArrowRight from './arrow-right'; + +import getDirectories from './directories'; +import getConfig from './config'; +import * as ReactDOM from 'react-dom/server'; +import stripHtml from 'string-strip-html'; +const config = getConfig(); +const directories = getDirectories(); +const TreeState = new Map(); +const titleType = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; +const MenuContext = createContext(false); + +const flatten = list => { + return list.reduce((flat, toFlatten) => { + return flat.concat( + toFlatten.children ? flatten(toFlatten.children) : toFlatten + ); + }, []); +}; + +const flatDirectories = flatten(directories); + +function Folder({ item, anchors }) { + const route = useRouter().route + '/'; + const active = route.startsWith(item.route + '/'); + const open = TreeState[item.route] ?? true; + const [_, render] = useState(false); + + useEffect(() => { + if (active) { + TreeState[item.route] = true; + } + }, [active]); + + return ( +
  • + +
    + +
    +
  • + ); +} + +function File({ item, anchors }) { + const { setMenu } = useContext(MenuContext); + const route = useRouter().route + '/'; + const active = route.startsWith(item.route + '/'); + + let title = item.title; + // if (item.title.startsWith('> ')) { + // title = title.substr(2) + if (anchors?.length) { + if (active) { + return ( +
  • + + {title} + + +
  • + ); + } + } + + return ( +
  • + + setMenu(false)} className="focus:shadow-outline"> + {title} + + +
  • + ); +} + +function Menu({ dir, anchors }) { + return ( + + ); +} + +function Sidebar({ show, anchors }) { + return ( + + ); +} + +const NextLink = ({ currentIndex }) => { + let next = flatDirectories[currentIndex + 1]; + + if (!config.nextLinks || !next) { + return null; + } + + return ( + + + {next.title} + + + + ); +}; + +const PrevLink = ({ currentIndex }) => { + let prev = flatDirectories[currentIndex - 1]; + + if (!config.prevLinks || !prev) { + return null; + } + + return ( + + + + {prev.title} + + + ); +}; + +const Layout = ({ filename, full, title: _title, ssg = {}, children }) => { + const [menu, setMenu] = useState(false); + const router = useRouter(); + const { route, pathname } = router; + + const filepath = route.slice(0, route.lastIndexOf('/') + 1); + const titles = React.Children.toArray(children).filter(child => + titleType.includes(child.props.mdxType) + ); + const anchors = titles + .filter(child => child.props.mdxType === 'h2') + .map(child => child.props.children); + + useEffect(() => { + if (menu) { + document.body.classList.add('overflow-hidden'); + } else { + document.body.classList.remove('overflow-hidden'); + } + }, [menu]); + + const currentIndex = useMemo( + () => flatDirectories.findIndex(dir => dir.route === pathname), + [flatDirectories, pathname] + ); + + const title = + flatDirectories[currentIndex]?.title || + titles.find(child => child.props.mdxType === 'h1')?.props.children || + 'Untitled'; + + const props = { + filepath: filepath + filename, + route, + }; + + return ( + <> + + + {title} + {config.titleSuffix || ''} + + {config.head ? config.head(props) : null} + +
    + +
    + + + + + {full ? ( + + {children} + + ) : ( + <> + + +
    + {children} + +
    {' '} +
    + + )} +
    +
    +
    + + ); +}; + +export default filename => { + return props => ; +}; diff --git a/website2/.nextra/nextra-loader.js b/website2/.nextra/nextra-loader.js new file mode 100644 index 000000000..b3a4dab9d --- /dev/null +++ b/website2/.nextra/nextra-loader.js @@ -0,0 +1,8 @@ +module.exports = function(source, map) { + this.cacheable() + const fileName = this.resourcePath.slice(this.resourcePath.lastIndexOf('/') + 1) + const prefix = `import withNextraLayout from '.nextra/layout'\n\n` + const suffix = `\n\nexport default withNextraLayout("${fileName}")` + source = prefix + source + suffix + this.callback(null, source, map) +} diff --git a/website2/.nextra/nextra.js b/website2/.nextra/nextra.js new file mode 100644 index 000000000..7b16277c1 --- /dev/null +++ b/website2/.nextra/nextra.js @@ -0,0 +1,33 @@ +const path = require('path') + +module.exports = (pluginOptions = { + extension: /\.mdx?$/ +}) => (nextConfig = { + pageExtensions: ['js', 'jsx', 'md', 'mdx'] +}) => { + const extension = pluginOptions.extension || /\.mdx$/ + + return Object.assign({}, nextConfig, { + webpack(config, options) { + config.module.rules.push({ + test: extension, + use: [ + options.defaultLoaders.babel, + { + loader: '@mdx-js/loader', + options: pluginOptions.options + }, + { + loader: path.resolve('.nextra', 'nextra-loader.js') + } + ] + }) + + if (typeof nextConfig.webpack === 'function') { + return nextConfig.webpack(config, options) + } + + return config + } + }) +} diff --git a/website2/.nextra/search.js b/website2/.nextra/search.js new file mode 100644 index 000000000..511f50b89 --- /dev/null +++ b/website2/.nextra/search.js @@ -0,0 +1,149 @@ +import { useMemo, useCallback, useRef, useState, useEffect } from 'react'; +import matchSorter from 'match-sorter'; +import cn from 'classnames'; +import { useRouter } from 'next/router'; +import Link from 'next/link'; + +const Item = ({ title, active, href, onMouseOver, search }) => { + const highlight = title.toLowerCase().indexOf(search.toLowerCase()); + + return ( + + +
  • + {title.substring(0, highlight)} + + {title.substring(highlight, highlight + search.length)} + + {title.substring(highlight + search.length)} +
  • +
    + + ); +}; + +const Search = ({ directories }) => { + const router = useRouter(); + const [show, setShow] = useState(false); + const [search, setSearch] = useState(''); + const [active, setActive] = useState(0); + const input = useRef(null); + + const results = useMemo(() => { + if (!search) return []; + + // Will need to scrape all the headers from each page and search through them here + // (similar to what we already do to render the hash links in sidebar) + // We could also try to search the entire string text from each page + return matchSorter(directories, search, { keys: ['title'] }); + }, [search]); + + const handleKeyDown = useCallback( + (e) => { + switch (e.key) { + case 'ArrowDown': { + e.preventDefault(); + if (active + 1 < results.length) { + setActive(active + 1); + } + break; + } + case 'ArrowUp': { + e.preventDefault(); + if (active - 1 >= 0) { + setActive(active - 1); + } + break; + } + case 'Enter': { + router.push(results[active].route); + break; + } + } + }, + [active, results, router] + ); + + useEffect(() => { + setActive(0); + }, [search]); + + useEffect(() => { + const inputs = ['input', 'select', 'button', 'textarea']; + + const down = (e) => { + if ( + document.activeElement && + inputs.indexOf(document.activeElement.tagName.toLowerCase() !== -1) + ) { + if (e.key === '/') { + e.preventDefault(); + input.current.focus(); + } else if (e.key === 'Escape') { + setShow(false); + } + } + }; + + window.addEventListener('keydown', down); + return () => window.removeEventListener('keydown', down); + }, []); + + const renderList = show && results.length > 0; + + return ( +
    + {renderList && ( +
    setShow(false)} /> + )} +
    + + + +
    + { + setSearch(e.target.value); + setShow(true); + }} + className="appearance-none pl-8 border rounded-md py-2 pr-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-full" + type="search" + placeholder='Search ("/" to focus)' + onKeyDown={handleKeyDown} + onFocus={() => setShow(true)} + ref={input} + aria-label="Search documentation" + /> + {renderList && ( +
      + {results.map((res, i) => { + return ( + setActive(i)} + /> + ); + })} +
    + )} +
    + ); +}; + +export default Search; diff --git a/website2/.nextra/ssg.js b/website2/.nextra/ssg.js new file mode 100644 index 000000000..2e4fb8f9a --- /dev/null +++ b/website2/.nextra/ssg.js @@ -0,0 +1,6 @@ +import { createContext, useContext } from 'react' + +const SSGContext = createContext({}) + +export default SSGContext +export const useSSG = () => useContext(SSGContext) diff --git a/website2/.nextra/styles.css b/website2/.nextra/styles.css new file mode 100644 index 000000000..50350ac2a --- /dev/null +++ b/website2/.nextra/styles.css @@ -0,0 +1,211 @@ +@tailwind base; + +html { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', + 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; +} +@supports (font-variation-settings: normal) { + html { + font-family: 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', + 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', + 'Helvetica Neue', sans-serif; + } +} + +html { + @apply subpixel-antialiased; + font-size: 16px; + font-feature-settings: 'rlig' 1, 'calt' 1, 'ss01' 1; +} +body { + @apply bg-white; +} + +h1 { + @apply text-4xl font-bold tracking-tight; +} +h2 { + @apply text-3xl font-semibold tracking-tight mt-10; + @apply border-b; +} +h3 { + @apply text-2xl font-semibold tracking-tight mt-8; +} +h4 { + @apply text-xl font-semibold tracking-tight mt-8; +} +h5 { + @apply text-lg font-semibold tracking-tight mt-8; +} +h6 { + @apply text-base font-semibold tracking-tight mt-8; +} +a { + @apply text-blue-600 underline; +} +p { + @apply mt-6 leading-7; +} +hr { + @apply my-8; +} +code { + @apply p-1 text-sm text-gray-800 bg-gray-500 bg-opacity-25 rounded; +} +pre { + @apply p-4 bg-gray-200 rounded-lg mt-6 mb-4 overflow-x-auto scrolling-touch; +} +pre code { + @apply p-0 text-black bg-transparent rounded-none; +} +a code { + @apply text-current no-underline; +} +figure { + @apply mb-8 relative; +} +video { + @apply absolute top-0 left-0; + cursor: pointer; +} +figure figcaption { + @apply text-sm text-gray-600; +} +@media (min-width: 768px) { + figure { + /* allow figures to overflow, but not exceeding the viewport width */ + @apply -mr-56; + max-width: calc(100vw - 20rem); + } +} + +table { + @apply my-8 w-full text-gray-700 text-sm; +} + +table > thead > tr { + @apply border-b border-t rounded-t-lg; +} + +table > thead > tr > th { + @apply px-3 py-2 text-left text-sm font-bold bg-gray-200 text-gray-700; +} + +table > tbody > tr { + @apply border-b; +} + +table > tbody > tr > td { + @apply p-3; +} + +table > tbody > tr > td:not(:first-child) > code { + @apply text-xs; +} + +table > tbody > tr > td > a > code, +table > tbody > tr > td > code { + @apply text-sm; +} +table > tbody > tr > td > a { + @apply text-blue-600 font-semibold transition-colors duration-150 ease-out; +} +table > tbody > tr > td > a:hover { + @apply text-blue-800 transition-colors duration-150 ease-out; +} +@tailwind components; +@tailwind utilities; + +.main-container { + min-height: 100vh; +} + +.sidebar { + @apply select-none; +} +.sidebar ul ul { + @apply ml-5; +} +.sidebar a:focus-visible, +.sidebar button:focus-visible { + @apply shadow-outline; +} +.sidebar li.active > a { + @apply font-semibold text-black bg-gray-200; +} +.sidebar button, +.sidebar a { + @apply block w-full text-left text-base text-black no-underline text-gray-600 mt-1 p-2 rounded select-none outline-none; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; +} +.sidebar a:hover, +.sidebar button:hover { + @apply text-gray-900 bg-gray-100; +} + +.sidebar .active-route > button { + @apply text-black font-medium; +} + +.sidebar .active-route > button:hover { + @apply text-current bg-transparent cursor-default; +} + +content ul { + @apply list-disc ml-6 mt-6; +} +content li { + @apply mt-2; +} +.subheading-anchor { + margin-top: -84px; + display: inline-block; + position: absolute; + width: 1px; +} + +.subheading-anchor + a:hover .anchor-icon, +.subheading-anchor + a:focus .anchor-icon { + opacity: 1; +} +.anchor-icon { + opacity: 0; + @apply ml-2 text-gray-500; +} + +h2 a { + @apply no-underline; +} +input[type='search']::-webkit-search-decoration, +input[type='search']::-webkit-search-cancel-button, +input[type='search']::-webkit-search-results-button, +input[type='search']::-webkit-search-results-decoration { + -webkit-appearance: none; +} + +.search-overlay { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--category-header span { + display: inline-block; +} +.algolia-autocomplete .ds-dropdown-menu { + width: 500px; + min-width: 300px; + max-width: calc(100vw - 50px); +} + +[data-reach-skip-link] { + @apply sr-only; +} + +[data-reach-skip-link]:focus { + @apply not-sr-only fixed ml-6 top-0 bg-white text-lg px-6 py-2 mt-2 outline-none shadow-outline z-50; +} diff --git a/website2/.nextra/theme.js b/website2/.nextra/theme.js new file mode 100644 index 000000000..e27834972 --- /dev/null +++ b/website2/.nextra/theme.js @@ -0,0 +1,194 @@ +import { MDXProvider } from '@mdx-js/react'; +import * as ReactDOM from 'react-dom/server'; +import Link from 'next/link'; +import Highlight, { defaultProps } from 'prism-react-renderer'; +import stripHtml from 'string-strip-html'; +import slugify from '@sindresorhus/slugify'; + +const THEME = { + plain: { + color: '#000', + backgroundColor: 'transparent', + }, + styles: [ + { + types: ['keyword'], + style: { + color: '#ff0078', + fontWeight: 'bold', + }, + }, + { + types: ['comment'], + style: { + color: '#999', + fontStyle: 'italic', + }, + }, + { + types: ['string', 'url', 'attr-value'], + style: { + color: '#028265', + }, + }, + { + types: ['builtin', 'char', 'constant', 'language-javascript'], + style: { + color: '#000', + }, + }, + { + types: ['attr-name'], + style: { + color: '#d9931e', + fontStyle: 'normal', + }, + }, + { + types: ['punctuation', 'operator'], + style: { + color: '#333', + }, + }, + { + types: ['number', 'function', 'tag'], + style: { + color: '#0076ff', + }, + }, + { + types: ['boolean', 'regex'], + style: { + color: '#d9931e', + }, + }, + ], +}; + +// Anchor links + +const HeaderLink = ({ tag: Tag, children, ...props }) => { + const slug = slugify(ReactDOM.renderToString(children) || ''); + return ( + + + + {children} + + # + + + + ); +}; + +const H2 = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +const H3 = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +const H4 = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +const H5 = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +const H6 = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +const A = ({ children, ...props }) => { + const isExternal = props.href?.startsWith('https://'); + if (isExternal) { + return ( + + {children} + + ); + } + return ( + + {children} + + ); +}; + +const Code = ({ children, className, highlight, ...props }) => { + if (!className) return {children}; + + const highlightedLines = highlight ? highlight.split(',').map(Number) : []; + + // https://mdxjs.com/guides/syntax-highlighting#all-together + const language = className.replace(/language-/, ''); + return ( + + {({ className, style, tokens, getLineProps, getTokenProps }) => ( + + {tokens.map((line, i) => ( +
    + {line.map((token, key) => ( + + ))} +
    + ))} +
    + )} +
    + ); +}; + +const components = { + h2: H2, + h3: H3, + h4: H4, + h5: H5, + h6: H6, + a: A, + code: Code, +}; + +export default ({ children }) => { + return {children}; +}; diff --git a/website2/components/features.js b/website2/components/features.js new file mode 100644 index 000000000..ab4e18687 --- /dev/null +++ b/website2/components/features.js @@ -0,0 +1,819 @@ +import React from 'react'; +import styles from './features.module.css'; + +const Feature = ({ text, icon }) => ( +
    + {icon} +

    {text}

    +
    +); + +export default () => ( +
    +

    + Zero-config CLI for TypeScript package development +

    +
    + CJS, ESM, UMD} + icon={ + + + + + } + /> + Treeshaking} + icon={ + + + + + + + + } + /> + + Live Playground + + } + icon={ + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + + + } + /> + + + + } + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + /> + + + + + + + } + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + /> + + + + + + + } + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + /> +
    +
    +); diff --git a/website2/components/features.module.css b/website2/components/features.module.css new file mode 100644 index 000000000..cba1aec08 --- /dev/null +++ b/website2/components/features.module.css @@ -0,0 +1,27 @@ +.features { + display: flex; + flex-wrap: wrap; + margin: 2.5rem -0.5rem 2rem; +} + +.feature { + flex: 0 0 33%; + align-items: center; + display: inline-flex; + padding: 0 0.5rem 1.5rem; + margin: 0 auto; +} +.feature h4 { + margin: 0 0 0 0.5rem; + font-weight: 700; + font-size: 0.95rem; + white-space: nowrap; +} +@media (max-width: 860px) { + .feature { + padding-left: 0; + } + .feature h4 { + font-size: 0.75rem; + } +} diff --git a/website2/components/logo.js b/website2/components/logo.js new file mode 100644 index 000000000..98be9e794 --- /dev/null +++ b/website2/components/logo.js @@ -0,0 +1,28 @@ +import React from 'react'; + +export const Logo = ({ height }) => ( + + + + + + + +); diff --git a/website2/components/video.js b/website2/components/video.js new file mode 100644 index 000000000..5fcbfe91d --- /dev/null +++ b/website2/components/video.js @@ -0,0 +1,52 @@ +import { useRef, useCallback, useEffect } from 'react' +import { useInView } from 'react-intersection-observer' +import 'intersection-observer' + +export default ({ src, caption, ratio }) => { + const [inViewRef, inView] = useInView({ + threshold: 1, + }) + const videoRef = useRef() + + const setRefs = useCallback( + (node) => { + // Ref's from useRef needs to have the node assigned to `current` + videoRef.current = node + // Callback refs, like the one from `useInView`, is a function that takes the node as an argument + inViewRef(node) + + if (node) { + node.addEventListener('click', function () { + if (this.paused) { + this.play() + } else { + this.pause() + } + }) + } + }, + [inViewRef] + ) + + useEffect(() => { + if (!videoRef || !videoRef.current) { + return + } + + if (inView) { + videoRef.current.play() + } else { + videoRef.current.pause() + } + }, [inView]) + + return ( +
    +
    + + {caption &&
    {caption}
    } +
    + ) +} diff --git a/website2/jsconfig.json b/website2/jsconfig.json new file mode 100644 index 000000000..b639b0f8f --- /dev/null +++ b/website2/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "baseUrl": "." + } +} \ No newline at end of file diff --git a/website2/next.config.js b/website2/next.config.js new file mode 100644 index 000000000..c09e915a8 --- /dev/null +++ b/website2/next.config.js @@ -0,0 +1,2 @@ +const withNextra = require('./.nextra/nextra')(); +module.exports = withNextra(); diff --git a/website2/nextra.config.js b/website2/nextra.config.js new file mode 100644 index 000000000..a0f69edca --- /dev/null +++ b/website2/nextra.config.js @@ -0,0 +1,90 @@ +import { Logo } from 'components/logo'; + +export default { + github: 'https://github.com/formium/tsdx', + titleSuffix: ' – TSDX', + logo: ( + <> + + TSDX + + ), + head: () => ( + <> + {/* Favicons, meta */} + + + + + + + + + + + + + + + + + + + {/* */} + + ), + footer: ({ filepath }) => ( + <> +
    + + A Jared Palmer Project + +
    + + Edit this page on GitHub + +
    + + ), +}; diff --git a/website2/package.json b/website2/package.json new file mode 100644 index 000000000..17281bc0e --- /dev/null +++ b/website2/package.json @@ -0,0 +1,37 @@ +{ + "name": "tsdx-site", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "next", + "start": "next", + "build": "next build" + }, + "author": "Jared Palmer", + "license": "MIT", + "dependencies": { + "@reach/skip-nav": "^0.10.5", + "@sindresorhus/slugify": "^1.0.0", + "classnames": "^2.2.6", + "focus-visible": "^5.1.0", + "intersection-observer": "^0.10.0", + "markdown-to-jsx": "^6.11.4", + "match-sorter": "^4.1.0", + "next": "^9.4.4", + "next-google-fonts": "^1.1.0", + "prism-react-renderer": "^1.1.1", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-intersection-observer": "^8.26.2", + "string-strip-html": "^4.5.0" + }, + "devDependencies": { + "@mdx-js/loader": "^1.6.5", + "babel-plugin-macros": "^2.8.0", + "postcss-preset-env": "^6.7.0", + "preval.macro": "^5.0.0", + "tailwindcss": "^1.4.6", + "title": "^3.4.2" + } +} diff --git a/website2/pages/_app.js b/website2/pages/_app.js new file mode 100644 index 000000000..391b3ab1a --- /dev/null +++ b/website2/pages/_app.js @@ -0,0 +1,14 @@ +import '.nextra/styles.css' +import GoogleFonts from 'next-google-fonts' + +export default function Nextra({ Component, pageProps }) { + return ( + <> + + + + ) +} diff --git a/website2/pages/_document.js b/website2/pages/_document.js new file mode 100644 index 000000000..19e669faa --- /dev/null +++ b/website2/pages/_document.js @@ -0,0 +1,25 @@ +import React from 'react' +import Document, { Html, Head, Main, NextScript } from 'next/document' +import { SkipNavLink } from '@reach/skip-nav' + +class MyDocument extends Document { + render() { + return ( + + + + +
    + +