From cf49090aef1305fabb36ee1b83394a40d53f89f8 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 31 May 2023 04:58:49 +0200 Subject: [PATCH] replace website (see comment) from old gatsby + mui to nextjs@13 rsc + tailwindcss --- apps/web/.env.example | 8 - apps/web/.eslintignore | 4 - apps/web/.eslintrc | 4 - apps/web/.gitignore | 35 --- apps/web/.prettierignore | 4 - apps/web/.prettierrc | 8 - apps/web/gatsby-browser.ts | 7 - apps/web/gatsby-config.ts | 118 ---------- apps/web/gatsby-node.ts | 23 -- apps/web/gatsby-ssr.tsx | 40 ---- apps/web/netlify.toml | 17 -- apps/web/package.json | 83 -------- apps/web/src/components/CopyButton.tsx | 60 ------ apps/web/src/components/backToTop.tsx | 47 ---- apps/web/src/components/code.tsx | 99 --------- apps/web/src/components/hero.tsx | 51 ----- apps/web/src/components/layout/footer.tsx | 72 ------- apps/web/src/components/layout/header.tsx | 113 ---------- apps/web/src/components/layout/index.tsx | 3 - apps/web/src/components/layout/layout.tsx | 112 ---------- apps/web/src/components/layout/sidebar.tsx | 138 ------------ .../src/components/layout/useSearchModal.ts | 16 -- apps/web/src/components/layout/useSidebar.ts | 49 ----- apps/web/src/components/mdxRenderer.tsx | 155 -------------- .../web/src/components/search/SearchModal.tsx | 110 ---------- apps/web/src/components/search/hitComps.tsx | 123 ----------- apps/web/src/components/search/input.tsx | 55 ----- apps/web/src/components/seo.tsx | 58 ----- apps/web/src/createPalette.d.ts | 17 -- apps/web/src/gatsby/createPages.ts | 7 - .../web/src/gatsby/createPages/createHooks.ts | 72 ------- apps/web/src/gatsby/onCreateNode.ts | 39 ---- apps/web/src/hooks/useHookList.ts | 35 --- apps/web/src/hooks/useSiteMetadata.ts | 34 --- apps/web/src/images/gatsby-icon.png | Bin 21212 -> 0 bytes apps/web/src/images/typescript.png | Bin 6821 -> 0 bytes apps/web/src/libs/algolia.ts | 86 -------- apps/web/src/libs/feedSerializer.ts | 51 ----- apps/web/src/libs/filterHooks.ts | 40 ---- apps/web/src/libs/prismjs-theme-dracula.ts | 78 ------- apps/web/src/libs/wrapPageElement.tsx | 9 - apps/web/src/models/Post.ts | 70 ------ apps/web/src/models/index.ts | 3 - apps/web/src/pages/404.tsx | 20 -- apps/web/src/pages/index.tsx | 12 -- apps/web/src/templates/post.tsx | 121 ----------- apps/web/src/theme.ts | 106 --------- apps/web/src/types.d.ts | 9 - apps/web/static/_redirects | 8 - apps/web/static/favicon.ico | Bin 3128 -> 0 bytes apps/web/static/robots.txt | 2 - apps/web/tsconfig.json | 23 -- apps/www/.eslintrc.json | 6 + apps/www/.gitignore | 38 ++++ apps/www/env.mjs | 12 ++ apps/www/next-sitemap.config.mjs | 23 ++ apps/www/next.config.mjs | 39 ++++ apps/www/package.json | 54 +++++ apps/www/postcss.config.js | 6 + apps/www/public/android-chrome-192x192.png | Bin 0 -> 6266 bytes apps/www/public/android-chrome-512x512.png | Bin 0 -> 26937 bytes apps/www/public/apple-touch-icon.png | Bin 0 -> 5594 bytes apps/www/public/favicon-16x16.png | Bin 0 -> 403 bytes apps/www/public/favicon-32x32.png | Bin 0 -> 786 bytes apps/www/public/favicon.ico | Bin 0 -> 15406 bytes apps/www/public/robots.txt | 9 + apps/www/public/site.webmanifest | 1 + apps/www/public/sitemap.xml | 41 ++++ apps/www/src/app/(docs)/introduction/page.tsx | 18 ++ apps/www/src/app/(docs)/layout.tsx | 53 +++++ .../src/app/(docs)/react-hook/[slug]/page.tsx | 96 +++++++++ apps/www/src/app/(marketing)/layout.tsx | 38 ++++ apps/www/src/app/(marketing)/page.tsx | 195 +++++++++++++++++ apps/www/src/app/globals.css | 84 ++++++++ apps/www/src/app/layout.tsx | 83 ++++++++ .../www/src/assets/fonts/CalSans-SemiBold.ttf | Bin 0 -> 148964 bytes .../src/assets/fonts/CalSans-SemiBold.woff | Bin 0 -> 52504 bytes .../src/assets/fonts/CalSans-SemiBold.woff2 | Bin 0 -> 40932 bytes apps/www/src/components/analytics.tsx | 23 ++ apps/www/src/components/docs-page-header.tsx | 25 +++ apps/www/src/components/icons.tsx | 86 ++++++++ apps/www/src/components/main-nav.tsx | 62 ++++++ apps/www/src/components/mobile-nav.tsx | 48 +++++ apps/www/src/components/mode-toggle.tsx | 44 ++++ apps/www/src/components/paper.tsx | 50 +++++ apps/www/src/components/remote-mdx.tsx | 193 +++++++++++++++++ apps/www/src/components/sidebar-nav.tsx | 67 ++++++ apps/www/src/components/table-of-content.tsx | 126 +++++++++++ apps/www/src/components/theme-provider.tsx | 10 + apps/www/src/components/ui/button.tsx | 56 +++++ apps/www/src/components/ui/dropdown-menu.tsx | 201 ++++++++++++++++++ apps/www/src/config/docs.ts | 31 +++ apps/www/src/config/marketing.ts | 14 ++ apps/www/src/config/site.ts | 13 ++ apps/www/src/hooks/use-lock-body.ts | 12 ++ apps/www/src/lib/mdx.ts | 55 +++++ apps/www/src/lib/utils.ts | 6 + apps/www/src/types/index.d.ts | 54 +++++ apps/www/tailwind.config.js | 81 +++++++ apps/www/tsconfig.json | 41 ++++ 100 files changed, 2094 insertions(+), 2454 deletions(-) delete mode 100644 apps/web/.env.example delete mode 100644 apps/web/.eslintignore delete mode 100644 apps/web/.eslintrc delete mode 100644 apps/web/.gitignore delete mode 100644 apps/web/.prettierignore delete mode 100644 apps/web/.prettierrc delete mode 100644 apps/web/gatsby-browser.ts delete mode 100644 apps/web/gatsby-config.ts delete mode 100644 apps/web/gatsby-node.ts delete mode 100644 apps/web/gatsby-ssr.tsx delete mode 100644 apps/web/netlify.toml delete mode 100644 apps/web/package.json delete mode 100644 apps/web/src/components/CopyButton.tsx delete mode 100644 apps/web/src/components/backToTop.tsx delete mode 100644 apps/web/src/components/code.tsx delete mode 100644 apps/web/src/components/hero.tsx delete mode 100644 apps/web/src/components/layout/footer.tsx delete mode 100644 apps/web/src/components/layout/header.tsx delete mode 100644 apps/web/src/components/layout/index.tsx delete mode 100644 apps/web/src/components/layout/layout.tsx delete mode 100644 apps/web/src/components/layout/sidebar.tsx delete mode 100644 apps/web/src/components/layout/useSearchModal.ts delete mode 100644 apps/web/src/components/layout/useSidebar.ts delete mode 100644 apps/web/src/components/mdxRenderer.tsx delete mode 100644 apps/web/src/components/search/SearchModal.tsx delete mode 100644 apps/web/src/components/search/hitComps.tsx delete mode 100644 apps/web/src/components/search/input.tsx delete mode 100644 apps/web/src/components/seo.tsx delete mode 100644 apps/web/src/createPalette.d.ts delete mode 100644 apps/web/src/gatsby/createPages.ts delete mode 100644 apps/web/src/gatsby/createPages/createHooks.ts delete mode 100644 apps/web/src/gatsby/onCreateNode.ts delete mode 100644 apps/web/src/hooks/useHookList.ts delete mode 100644 apps/web/src/hooks/useSiteMetadata.ts delete mode 100644 apps/web/src/images/gatsby-icon.png delete mode 100644 apps/web/src/images/typescript.png delete mode 100644 apps/web/src/libs/algolia.ts delete mode 100644 apps/web/src/libs/feedSerializer.ts delete mode 100644 apps/web/src/libs/filterHooks.ts delete mode 100644 apps/web/src/libs/prismjs-theme-dracula.ts delete mode 100644 apps/web/src/libs/wrapPageElement.tsx delete mode 100644 apps/web/src/models/Post.ts delete mode 100644 apps/web/src/models/index.ts delete mode 100644 apps/web/src/pages/404.tsx delete mode 100644 apps/web/src/pages/index.tsx delete mode 100644 apps/web/src/templates/post.tsx delete mode 100644 apps/web/src/theme.ts delete mode 100644 apps/web/src/types.d.ts delete mode 100644 apps/web/static/_redirects delete mode 100644 apps/web/static/favicon.ico delete mode 100644 apps/web/static/robots.txt delete mode 100644 apps/web/tsconfig.json create mode 100644 apps/www/.eslintrc.json create mode 100644 apps/www/.gitignore create mode 100644 apps/www/env.mjs create mode 100644 apps/www/next-sitemap.config.mjs create mode 100644 apps/www/next.config.mjs create mode 100644 apps/www/package.json create mode 100644 apps/www/postcss.config.js create mode 100644 apps/www/public/android-chrome-192x192.png create mode 100644 apps/www/public/android-chrome-512x512.png create mode 100644 apps/www/public/apple-touch-icon.png create mode 100644 apps/www/public/favicon-16x16.png create mode 100644 apps/www/public/favicon-32x32.png create mode 100644 apps/www/public/favicon.ico create mode 100644 apps/www/public/robots.txt create mode 100644 apps/www/public/site.webmanifest create mode 100644 apps/www/public/sitemap.xml create mode 100644 apps/www/src/app/(docs)/introduction/page.tsx create mode 100644 apps/www/src/app/(docs)/layout.tsx create mode 100644 apps/www/src/app/(docs)/react-hook/[slug]/page.tsx create mode 100644 apps/www/src/app/(marketing)/layout.tsx create mode 100644 apps/www/src/app/(marketing)/page.tsx create mode 100644 apps/www/src/app/globals.css create mode 100644 apps/www/src/app/layout.tsx create mode 100644 apps/www/src/assets/fonts/CalSans-SemiBold.ttf create mode 100644 apps/www/src/assets/fonts/CalSans-SemiBold.woff create mode 100644 apps/www/src/assets/fonts/CalSans-SemiBold.woff2 create mode 100644 apps/www/src/components/analytics.tsx create mode 100644 apps/www/src/components/docs-page-header.tsx create mode 100644 apps/www/src/components/icons.tsx create mode 100644 apps/www/src/components/main-nav.tsx create mode 100644 apps/www/src/components/mobile-nav.tsx create mode 100644 apps/www/src/components/mode-toggle.tsx create mode 100644 apps/www/src/components/paper.tsx create mode 100644 apps/www/src/components/remote-mdx.tsx create mode 100644 apps/www/src/components/sidebar-nav.tsx create mode 100644 apps/www/src/components/table-of-content.tsx create mode 100644 apps/www/src/components/theme-provider.tsx create mode 100644 apps/www/src/components/ui/button.tsx create mode 100644 apps/www/src/components/ui/dropdown-menu.tsx create mode 100644 apps/www/src/config/docs.ts create mode 100644 apps/www/src/config/marketing.ts create mode 100644 apps/www/src/config/site.ts create mode 100644 apps/www/src/hooks/use-lock-body.ts create mode 100644 apps/www/src/lib/mdx.ts create mode 100644 apps/www/src/lib/utils.ts create mode 100644 apps/www/src/types/index.d.ts create mode 100644 apps/www/tailwind.config.js create mode 100644 apps/www/tsconfig.json diff --git a/apps/web/.env.example b/apps/web/.env.example deleted file mode 100644 index c2964d4f..00000000 --- a/apps/web/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# rename this file to .env and supply the values listed below -# also make sure they are available to the build tool (e.g. Netlify) -# warning: variables prefixed with GATSBY_ will be made available to client-side code -# be careful not to expose sensitive data (in this case your Algolia admin key) - -GATSBY_ALGOLIA_APP_ID=insertValue -GATSBY_ALGOLIA_SEARCH_KEY=insertValue -GATSBY_ALGOLIA_ADMIN_KEY=insertValue diff --git a/apps/web/.eslintignore b/apps/web/.eslintignore deleted file mode 100644 index 48732763..00000000 --- a/apps/web/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -.cache -public -generated diff --git a/apps/web/.eslintrc b/apps/web/.eslintrc deleted file mode 100644 index 9679d408..00000000 --- a/apps/web/.eslintrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": ["custom"], - "root": true -} diff --git a/apps/web/.gitignore b/apps/web/.gitignore deleted file mode 100644 index b68eb805..00000000 --- a/apps/web/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -node_modules - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -npm-debug.log* -*.tsbuildinfo - -# Cache -.npm -.cache -.eslintcache - -# Compiled stuff -public -generated - -# Coverage -coverage - -# dotenv environment variable files -.env* -!.env.example - -# Output of 'npm pack' -*.tgz - -# Mac files -.DS_Store - -# Local Netlify folder -.netlify diff --git a/apps/web/.prettierignore b/apps/web/.prettierignore deleted file mode 100644 index 48732763..00000000 --- a/apps/web/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -.cache -public -generated diff --git a/apps/web/.prettierrc b/apps/web/.prettierrc deleted file mode 100644 index 772c4596..00000000 --- a/apps/web/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "arrowParens": "avoid", - "semi": false, - "printWidth": 80, - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "all" -} diff --git a/apps/web/gatsby-browser.ts b/apps/web/gatsby-browser.ts deleted file mode 100644 index 00c2d5fc..00000000 --- a/apps/web/gatsby-browser.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Implement Gatsby's Browser APIs in this file. - * - * See: https://www.gatsbyjs.org/docs/browser-apis/ - */ - -export { default as wrapPageElement } from './src/libs/wrapPageElement' diff --git a/apps/web/gatsby-config.ts b/apps/web/gatsby-config.ts deleted file mode 100644 index 0bbb9abe..00000000 --- a/apps/web/gatsby-config.ts +++ /dev/null @@ -1,118 +0,0 @@ -import dotenv from 'dotenv' -import type { GatsbyConfig } from 'gatsby' -import path from 'path' - -// import * as algolia from './src/libs/algolia' -// import * as feed from './src/libs/feedSerializer' - -dotenv.config({ path: `.env.${process.env.NODE_ENV}` }) - -const siteMetadata = { - title: `usehooks-ts`, - description: `Welcome to the documentation of usehooks-ts, a React hooks library, ready to use, written in typescript.`, - siteUrl: `https://usehooks-ts.com`, - author: `juliencrn`, // Github username -} - -const config: GatsbyConfig = { - siteMetadata, - plugins: [ - `gatsby-plugin-typescript`, - `gatsby-plugin-catch-links`, - `gatsby-plugin-material-ui`, - `gatsby-plugin-sitemap`, - { - resolve: 'gatsby-plugin-root-import', - options: { - '~': path.join(__dirname, 'src'), - }, - }, - { - resolve: `gatsby-plugin-mdx`, - options: { - extensions: [`.md`, `.mdx`], - gatsbyRemarkPlugins: [ - { - resolve: 'remark-codesandbox/gatsby', - options: { - mode: 'button', - }, - }, - ], - }, - }, - { - resolve: `gatsby-source-filesystem`, - options: { - path: `${__dirname}/generated/posts`, - name: `posts`, - }, - }, - { - resolve: `gatsby-source-filesystem`, - options: { - path: `${__dirname}/generated/hooks`, - name: `hooks`, - }, - }, - { - resolve: `gatsby-source-filesystem`, - options: { - path: `${__dirname}/generated/demos`, - name: `demos`, - }, - }, - // { - // resolve: `gatsby-plugin-algolia`, - // options: { - // appId: process.env.GATSBY_ALGOLIA_APP_ID, - // apiKey: process.env.GATSBY_ALGOLIA_ADMIN_KEY, - // queries: algolia.queries, - // }, - // }, - // { - // resolve: `gatsby-plugin-feed`, - // options: { - // feeds: [ - // { - // query: `${feed.query}`, - // output: '/rss.xml', - // title: `RSS Feed - ${siteMetadata.title}`, - // description: `${siteMetadata.description}`, - // serialize: ({ query }: feed.SerializeProps) => - // feed.serializer({ query, siteMetadata }), - // }, - // ], - // }, - // }, - { - resolve: `gatsby-plugin-google-analytics`, - options: { - trackingId: `UA-132477935-3`, - head: false, // Puts script in the head instead of the body - anonymize: true, - }, - }, - { - resolve: 'gatsby-plugin-robots-txt', - options: { - host: siteMetadata.siteUrl, - policy: [{ userAgent: '*', allow: '/' }], - }, - }, - { - resolve: `gatsby-plugin-manifest`, - options: { - name: `gatsby-starter-default`, - short_name: `starter`, - start_url: `/`, - background_color: `#007ACC`, - theme_color: `#007ACC`, - display: `minimal-ui`, - icon: `src/images/typescript.png`, // This path is relative to the root of the site. - }, - }, - ], -} - -export default config diff --git a/apps/web/gatsby-node.ts b/apps/web/gatsby-node.ts deleted file mode 100644 index e99032ad..00000000 --- a/apps/web/gatsby-node.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Implement Gatsby's Node APIs in this file. - * - * See: https://www.gatsbyjs.org/docs/node-apis/ - */ - -import { GatsbyNode } from 'gatsby' - -export { createPages } from './src/gatsby/createPages' -export { onCreateNode } from './src/gatsby/onCreateNode' - -// importing React in now not required since React 17 and Gatsby 2.28.1, -// but the ecosystem of plugins doesn't support it all yet -export const onCreateBabelConfig: GatsbyNode['onCreateBabelConfig'] = ({ - actions, -}) => { - actions.setBabelPlugin({ - name: '@babel/plugin-transform-react-jsx', - options: { - runtime: 'automatic', - }, - }) -} diff --git a/apps/web/gatsby-ssr.tsx b/apps/web/gatsby-ssr.tsx deleted file mode 100644 index 471d42f0..00000000 --- a/apps/web/gatsby-ssr.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. - * - * See: https://www.gatsbyjs.org/docs/ssr-apis/ - */ - -import React, { Fragment } from 'react' - -import { GatsbySSR } from 'gatsby' - -const GoogleAdsTag = ( - + + ) +} diff --git a/apps/www/src/components/docs-page-header.tsx b/apps/www/src/components/docs-page-header.tsx new file mode 100644 index 00000000..87a835fd --- /dev/null +++ b/apps/www/src/components/docs-page-header.tsx @@ -0,0 +1,25 @@ +import { cn } from '@/lib/utils' + +interface DocsPageHeaderProps extends React.HTMLAttributes { + heading: string + text?: string +} + +export function DocsPageHeader({ + heading, + text, + className, + ...props +}: DocsPageHeaderProps) { + return ( + <> +
+

+ {heading} +

+ {text &&

{text}

} +
+
+ + ) +} diff --git a/apps/www/src/components/icons.tsx b/apps/www/src/components/icons.tsx new file mode 100644 index 00000000..018d1321 --- /dev/null +++ b/apps/www/src/components/icons.tsx @@ -0,0 +1,86 @@ +import { + type Icon as LucideIcon, + AlertTriangle, + ArrowRight, + BookOpenCheck, + Check, + ChevronLeft, + ChevronRight, + Code2, + CreditCard, + File, + FileText, + Flame, + Globe, + HelpCircle, + Image, + Laptop, + Leaf, + Loader2, + LucideProps, + Moon, + MoreVertical, + Pizza, + Plus, + Puzzle, + Settings, + SunMedium, + Trash, + Twitter, + Unplug, + User, + X, + Zap, +} from 'lucide-react' + +export type Icon = LucideIcon + +export const Icons = { + logo: Flame, + close: X, + spinner: Loader2, + chevronLeft: ChevronLeft, + chevronRight: ChevronRight, + trash: Trash, + post: FileText, + page: File, + media: Image, + settings: Settings, + billing: CreditCard, + ellipsis: MoreVertical, + add: Plus, + warning: AlertTriangle, + user: User, + arrowRight: ArrowRight, + help: HelpCircle, + pizza: Pizza, + sun: SunMedium, + moon: Moon, + laptop: Laptop, + zap: Zap, + leaf: Leaf, + globe: Globe, + code: Code2, + book: BookOpenCheck, + unplug: Unplug, + puzzle: Puzzle, + gitHub: ({ ...props }: LucideProps) => ( + + ), + twitter: Twitter, + check: Check, +} diff --git a/apps/www/src/components/main-nav.tsx b/apps/www/src/components/main-nav.tsx new file mode 100644 index 00000000..a7840560 --- /dev/null +++ b/apps/www/src/components/main-nav.tsx @@ -0,0 +1,62 @@ +'use client' + +import * as React from 'react' + +import Link from 'next/link' +import { useSelectedLayoutSegment } from 'next/navigation' + +import { Icons } from '@/components/icons' +import { MobileNav } from '@/components/mobile-nav' +import { siteConfig } from '@/config/site' +import { cn } from '@/lib/utils' +import { MainNavItem } from '@/types' + +interface MainNavProps { + items?: MainNavItem[] + children?: React.ReactNode +} + +export function MainNav({ items, children }: MainNavProps) { + const segment = useSelectedLayoutSegment() + const [showMobileMenu, setShowMobileMenu] = React.useState(false) + + return ( +
+ + + + {siteConfig.name} + + + {items?.length ? ( + + ) : null} + + {showMobileMenu && items && ( + {children} + )} +
+ ) +} diff --git a/apps/www/src/components/mobile-nav.tsx b/apps/www/src/components/mobile-nav.tsx new file mode 100644 index 00000000..1fe97289 --- /dev/null +++ b/apps/www/src/components/mobile-nav.tsx @@ -0,0 +1,48 @@ +import * as React from 'react' + +import Link from 'next/link' + +import { Icons } from '@/components/icons' +import { siteConfig } from '@/config/site' +import { useLockBody } from '@/hooks/use-lock-body' +import { cn } from '@/lib/utils' +import { MainNavItem } from '@/types' + +interface MobileNavProps { + items: MainNavItem[] + children?: React.ReactNode +} + +export function MobileNav({ items, children }: MobileNavProps) { + useLockBody() + + return ( +
+
+ + + {siteConfig.name} + + + {children} +
+
+ ) +} diff --git a/apps/www/src/components/mode-toggle.tsx b/apps/www/src/components/mode-toggle.tsx new file mode 100644 index 00000000..568c551e --- /dev/null +++ b/apps/www/src/components/mode-toggle.tsx @@ -0,0 +1,44 @@ +'use client' + +import * as React from 'react' + +import { useTheme } from 'next-themes' + +import { Icons } from '@/components/icons' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' + +export function ModeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme('light')}> + + Light + + setTheme('dark')}> + + Dark + + setTheme('system')}> + + System + + + + ) +} diff --git a/apps/www/src/components/paper.tsx b/apps/www/src/components/paper.tsx new file mode 100644 index 00000000..91d0abec --- /dev/null +++ b/apps/www/src/components/paper.tsx @@ -0,0 +1,50 @@ +import Link from 'next/link' + +import { Icons } from '@/components/icons' +import { buttonVariants } from '@/components/ui/button' +import { getPosts } from '@/lib/mdx' +import { cn } from '@/lib/utils' + +interface DocsPagerProps { + slug: string +} + +export function DocsPager({ slug }: DocsPagerProps) { + const pager = getPagerForDoc(slug) + + if (!pager) { + return null + } + + return ( +
+ {pager?.prev && ( + + + {pager.prev.name} + + )} + {pager?.next && ( + + {pager.next.name} + + + )} +
+ ) +} + +export function getPagerForDoc(slug: string) { + const posts = getPosts() + const activeIndex = posts.findIndex(post => post.slug === slug) + const prev = activeIndex !== 0 ? posts[activeIndex - 1] : null + const next = activeIndex !== posts.length - 1 ? posts[activeIndex + 1] : null + + return { prev, next } +} diff --git a/apps/www/src/components/remote-mdx.tsx b/apps/www/src/components/remote-mdx.tsx new file mode 100644 index 00000000..3594996a --- /dev/null +++ b/apps/www/src/components/remote-mdx.tsx @@ -0,0 +1,193 @@ +/* eslint-disable jsx-a11y/heading-has-content */ +import 'highlight.js/styles/github-dark.css' + +import { ComponentProps } from 'react' + +import Link from 'next/link' +import { SerializeOptions } from 'next-mdx-remote/dist/types' +import { MDXRemote } from 'next-mdx-remote/rsc' +import rehypeHighlight from 'rehype-highlight' + +import { cn } from '@/lib/utils' + +export const H1 = ({ className, ...props }: ComponentProps<'h1'>) => ( +

+) + +export const H2 = ({ className, ...props }: ComponentProps<'h2'>) => ( +

+) + +export const H3 = ({ className, ...props }: ComponentProps<'h3'>) => ( +

+) + +const isInternal = (href?: string): href is string => !!href?.startsWith('/') + +export const SmartLink = ({ + className, + href, + children, + ...props +}: ComponentProps<'a'>) => { + const classes = cn('font-medium underline underline-offset-4', className) + if (isInternal(href)) { + return ( + + {children} + + ) + } + + return ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ) +} + +const components = { + h1: H1, + h2: H2, + h3: H3, + h4: ({ className, ...props }: ComponentProps<'h4'>) => ( +

+ ), + h5: ({ className, ...props }: ComponentProps<'h5'>) => ( +

+ ), + h6: ({ className, ...props }: ComponentProps<'h6'>) => ( +
+ ), + a: SmartLink, + p: ({ className, ...props }: ComponentProps<'p'>) => ( +

+ ), + ul: ({ className, ...props }: ComponentProps<'ul'>) => ( +

    + ), + ol: ({ className, ...props }: ComponentProps<'ol'>) => ( +
      + ), + li: ({ className, ...props }: ComponentProps<'li'>) => ( +
    1. + ), + blockquote: ({ className, ...props }: ComponentProps<'blockquote'>) => ( +
      *]:text-muted-foreground', + className, + )} + {...props} + /> + ), + img: ({ className, alt, ...props }: ComponentProps<'img'>) => ( + {alt} + ), + hr: ({ ...props }: ComponentProps<'hr'>) => ( +
      + ), + table: ({ className, ...props }: ComponentProps<'table'>) => ( +
      + + + ), + tr: ({ className, ...props }: ComponentProps<'tr'>) => ( + + ), + th: ({ className, ...props }: ComponentProps<'th'>) => ( +
      + ), + td: ({ className, ...props }: ComponentProps<'td'>) => ( + + ), + pre: ({ className, ...props }: ComponentProps<'pre'>) => ( +
      +  ),
      +  code: ({ className, ...props }: ComponentProps<'code'>) => (
      +    
      +  ),
      +}
      +
      +export function Mdx(props: { source: Buffer }) {
      +  const options: SerializeOptions = {
      +    mdxOptions: {
      +      rehypePlugins: [rehypeHighlight],
      +    },
      +  }
      +
      +  return (
      +    <>
      +      {/* @ts-expect-error Async Server Component */}
      +      
      +    
      +  )
      +}
      diff --git a/apps/www/src/components/sidebar-nav.tsx b/apps/www/src/components/sidebar-nav.tsx
      new file mode 100644
      index 00000000..85b11fb9
      --- /dev/null
      +++ b/apps/www/src/components/sidebar-nav.tsx
      @@ -0,0 +1,67 @@
      +'use client'
      +
      +import Link from 'next/link'
      +import { usePathname } from 'next/navigation'
      +
      +import { cn } from '@/lib/utils'
      +import { SidebarNavItem } from '@/types'
      +
      +export interface DocsSidebarNavProps {
      +  items: SidebarNavItem[]
      +}
      +
      +export function DocsSidebarNav({ items }: DocsSidebarNavProps) {
      +  const pathname = usePathname()
      +
      +  return items.length ? (
      +    
      + {items.map((item, index) => ( +
      +

      + {item.title} +

      + {item.items ? ( + + ) : null} +
      + ))} +
      + ) : null +} + +interface DocsSidebarNavItemsProps { + items: SidebarNavItem[] + pathname: string | null +} + +export function DocsSidebarNavItems({ + items, + pathname, +}: DocsSidebarNavItemsProps) { + return items?.length ? ( +
      + {items.map((item, index) => + !item.disabled && item.href ? ( + + {item.title} + + ) : ( + + {item.title} + + ), + )} +
      + ) : null +} diff --git a/apps/www/src/components/table-of-content.tsx b/apps/www/src/components/table-of-content.tsx new file mode 100644 index 00000000..5774056c --- /dev/null +++ b/apps/www/src/components/table-of-content.tsx @@ -0,0 +1,126 @@ +'use client' + +import * as React from 'react' + +import { useIsMounted } from 'usehooks-ts' + +import { cn } from '@/lib/utils' + +interface Item { + title: string + url: string + items?: Item[] +} + +interface Items { + items?: Item[] +} + +export type TableOfContents = Items + +interface TocProps { + toc: TableOfContents +} + +export function DashboardTableOfContents({ toc }: TocProps) { + const itemIds = React.useMemo( + () => + toc.items + ? toc.items + .flatMap(item => [item.url, item?.items?.map(item => item.url)]) + .flat() + .filter(Boolean) + .map(id => id?.split('#')[1]) + : [], + [toc], + ) + const activeHeading = useActiveItem(itemIds) + const isMounted = useIsMounted() + + if (!toc?.items) { + return null + } + + return isMounted() ? ( +
      +

      On This Page

      + +
      + ) : null +} + +function useActiveItem(itemIds: (string | undefined)[]) { + const [activeId, setActiveId] = React.useState('') + + React.useEffect(() => { + const observer = new IntersectionObserver( + entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + setActiveId(entry.target.id) + } + }) + }, + { rootMargin: `0% 0% -80% 0%` }, + ) + + itemIds?.forEach(id => { + if (!id) { + return + } + + const element = document.getElementById(id) + if (element) { + observer.observe(element) + } + }) + + return () => { + itemIds?.forEach(id => { + if (!id) { + return + } + + const element = document.getElementById(id) + if (element) { + observer.unobserve(element) + } + }) + } + }, [itemIds]) + + return activeId +} + +interface TreeProps { + tree: TableOfContents + level?: number + activeItem?: string | null +} + +function Tree({ tree, level = 1, activeItem }: TreeProps) { + return tree?.items?.length && level < 3 ? ( +
        + {tree.items.map((item, index) => { + return ( +
      • + + {item.title} + + {item.items?.length ? ( + + ) : null} +
      • + ) + })} +
      + ) : null +} diff --git a/apps/www/src/components/theme-provider.tsx b/apps/www/src/components/theme-provider.tsx new file mode 100644 index 00000000..6d502add --- /dev/null +++ b/apps/www/src/components/theme-provider.tsx @@ -0,0 +1,10 @@ +'use client' + +import * as React from 'react' + +import { ThemeProvider as NextThemesProvider } from 'next-themes' +import { ThemeProviderProps } from 'next-themes/dist/types' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/apps/www/src/components/ui/button.tsx b/apps/www/src/components/ui/button.tsx new file mode 100644 index 00000000..d34f532a --- /dev/null +++ b/apps/www/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from 'react' + +import { Slot } from '@radix-ui/react-slot' +import { type VariantProps, cva } from 'class-variance-authority' + +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: + 'border border-input hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'underline-offset-4 hover:underline text-primary', + }, + size: { + default: 'h-10 py-2 px-4', + sm: 'h-9 px-3 rounded-md', + lg: 'h-11 px-8 rounded-md', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button' + return ( + + ) + }, +) +Button.displayName = 'Button' + +export { Button, buttonVariants } diff --git a/apps/www/src/components/ui/dropdown-menu.tsx b/apps/www/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..705bd489 --- /dev/null +++ b/apps/www/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,201 @@ +'use client' + +import * as React from 'react' + +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' +import { Check, ChevronRight, Circle } from 'lucide-react' + +import { cn } from '@/lib/utils' + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut' + +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} diff --git a/apps/www/src/config/docs.ts b/apps/www/src/config/docs.ts new file mode 100644 index 00000000..07007f4b --- /dev/null +++ b/apps/www/src/config/docs.ts @@ -0,0 +1,31 @@ +import { getPosts } from '@/lib/mdx' +import { DocsConfig, NavItem } from '@/types' + +export const hookNavItems: NavItem[] = getPosts().map(post => ({ + title: post.name, + href: post.href, +})) + +export const docsConfig: DocsConfig = { + mainNav: [ + { + title: 'Documentation', + href: '/introduction', + }, + ], + sidebarNav: [ + { + title: 'Getting Started', + items: [ + { + title: 'Introduction', + href: '/introduction', + }, + ], + }, + { + title: 'Hooks', + items: hookNavItems, + }, + ], +} diff --git a/apps/www/src/config/marketing.ts b/apps/www/src/config/marketing.ts new file mode 100644 index 00000000..2e9f3630 --- /dev/null +++ b/apps/www/src/config/marketing.ts @@ -0,0 +1,14 @@ +import { MarketingConfig } from '@/types' + +export const marketingConfig: MarketingConfig = { + mainNav: [ + { + title: 'Features', + href: '/#features', + }, + { + title: 'Documentation', + href: '/introduction', + }, + ], +} diff --git a/apps/www/src/config/site.ts b/apps/www/src/config/site.ts new file mode 100644 index 00000000..58818f3d --- /dev/null +++ b/apps/www/src/config/site.ts @@ -0,0 +1,13 @@ +import { SiteConfig } from '@/types' + +export const siteConfig: SiteConfig = { + name: 'usehooks-ts', + description: 'React hook library, ready to use, written in Typescript.', + url: 'https://usehooks-ts.com', + ogImage: + 'https://via.placeholder.com/1200x630.png/007ACC/fff/?text=usehooks-ts', + links: { + github: 'https://github.com/juliencrn/usehooks-ts', + npm: 'https://www.npmjs.com/package/usehooks-ts', + }, +} diff --git a/apps/www/src/hooks/use-lock-body.ts b/apps/www/src/hooks/use-lock-body.ts new file mode 100644 index 00000000..3ab094a5 --- /dev/null +++ b/apps/www/src/hooks/use-lock-body.ts @@ -0,0 +1,12 @@ +import * as React from 'react' + +// @see https://usehooks.com/useLockBodyScroll. +export function useLockBody() { + React.useLayoutEffect((): (() => void) => { + const originalStyle: string = window.getComputedStyle( + document.body, + ).overflow + document.body.style.overflow = 'hidden' + return () => (document.body.style.overflow = originalStyle) + }, []) +} diff --git a/apps/www/src/lib/mdx.ts b/apps/www/src/lib/mdx.ts new file mode 100644 index 00000000..b4e1d7d5 --- /dev/null +++ b/apps/www/src/lib/mdx.ts @@ -0,0 +1,55 @@ +import fs from 'fs' +import path from 'path' + +import { Option, Post } from '@/types' + +const GENERATED_PATH = path.resolve(process.cwd(), 'generated') + +const allPosts = fs + .readdirSync(`${GENERATED_PATH}/posts`) + .filter(filename => /\.md?$/.test(filename)) + +const getHook = (name: string): Option => { + const pathname = path.join(GENERATED_PATH, 'hooks', `${name}.md`) + return readFile(pathname) +} + +const getDemo = (name: string): Option => { + const pathname = path.join(GENERATED_PATH, 'demos', `${name}.md`) + return readFile(pathname) +} + +const readFile = (pathname: string): Option => { + try { + return fs.readFileSync(pathname) + } catch (error) { + console.warn(`Document not found: ${pathname}`) + return null + } +} + +export const getPosts = (): Post[] => { + return allPosts + .map(filename => { + const name = filename.replace(/\.mdx?$/, '') + const slug = name.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) + const href = `/react-hook/${slug}` + const pathname = path.join(GENERATED_PATH, 'posts', filename) + const docs = readFile(pathname) + const hook = getHook(name) + const demo = getDemo(name) + return { name, slug, href, docs, hook, demo } + }) + .filter(post => post.docs && post.hook && post.demo) + .sort((a, b) => { + if (a.name < b.name) return -1 + if (a.name > b.name) return 1 + return 0 + }) as Post[] +} + +export const getPost = (slug: string): Option => { + const allPosts = getPosts() + const post = allPosts.find(post => post.slug === slug) + return post || null +} diff --git a/apps/www/src/lib/utils.ts b/apps/www/src/lib/utils.ts new file mode 100644 index 00000000..5589c557 --- /dev/null +++ b/apps/www/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/apps/www/src/types/index.d.ts b/apps/www/src/types/index.d.ts new file mode 100644 index 00000000..a58eb044 --- /dev/null +++ b/apps/www/src/types/index.d.ts @@ -0,0 +1,54 @@ +export type SiteConfig = { + name: string + description: string + url: string + ogImage: string + links: { + github: string + npm: string + } +} + +export type NavItem = { + title: string + href: string + disabled?: boolean +} + +export type MainNavItem = NavItem + +export type SidebarNavItem = { + title: string + disabled?: boolean + external?: boolean + icon?: keyof typeof Icons +} & ( + | { + href: string + items?: never + } + | { + href?: string + items: NavLink[] + } +) + +export type DocsConfig = { + mainNav: MainNavItem[] + sidebarNav: SidebarNavItem[] +} + +export type MarketingConfig = { + mainNav: MainNavItem[] +} + +export interface Post { + name: string // useHook + slug: string // use-hook + href: string // /react-hook/use-hook + docs: Buffer // markdown raw + hook: Buffer // markdown raw + demo: Buffer // markdown raw +} + +export type Option = T | null diff --git a/apps/www/tailwind.config.js b/apps/www/tailwind.config.js new file mode 100644 index 00000000..f2605b80 --- /dev/null +++ b/apps/www/tailwind.config.js @@ -0,0 +1,81 @@ +const { fontFamily } = require('tailwindcss/defaultTheme') + +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ['class'], + content: [ + './src/pages/**/*.{ts,tsx}', + './src/components/**/*.{ts,tsx}', + './src/app/**/*.{ts,tsx,css}', + ], + theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', + }, + }, + extend: { + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + fontFamily: { + sans: ['var(--font-sans)', ...fontFamily.sans], + heading: ['var(--font-heading)', ...fontFamily.sans], + }, + keyframes: { + 'accordion-down': { + from: { height: 0 }, + to: { height: 'var(--radix-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--radix-accordion-content-height)' }, + to: { height: 0 }, + }, + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + }, + }, + }, + plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')], +} diff --git a/apps/www/tsconfig.json b/apps/www/tsconfig.json new file mode 100644 index 00000000..ce5a3e81 --- /dev/null +++ b/apps/www/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ], + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ], + "exclude": [ + "node_modules", + ] +}