diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 2fb9ad7943345..51d848de12dc9 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -77,23 +77,6 @@ jobs: steps: - run: exit 0 - testMacOS: - name: macOS (Basic, Production, Acceptance) - runs-on: macos-latest - env: - NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - HEADLESS: true - - steps: - - uses: actions/checkout@v2 - - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - # Installing dependencies again since OS changed - - run: yarn install --frozen-lockfile --check-files || yarn install --frozen-lockfile --check-files - - run: node run-tests.js test/integration/production/test/index.test.js - - run: node run-tests.js test/integration/basic/test/index.test.js - - run: node run-tests.js test/acceptance/* - testWebpack5: name: webpack 5 (Basic, Production, Acceptance) runs-on: ubuntu-latest diff --git a/.github/workflows/test_macos.yml b/.github/workflows/test_macos.yml new file mode 100644 index 0000000000000..09ff576abd913 --- /dev/null +++ b/.github/workflows/test_macos.yml @@ -0,0 +1,22 @@ +on: + push: + branches: [canary] + +name: Test macOS + +jobs: + testMacOS: + name: macOS (Basic, Production, Acceptance) + runs-on: macos-latest + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + HEADLESS: true + + steps: + - uses: actions/checkout@v2 + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - run: yarn install --frozen-lockfile --check-files || yarn install --frozen-lockfile --check-files + - run: node run-tests.js test/integration/production/test/index.test.js + - run: node run-tests.js test/integration/basic/test/index.test.js + - run: node run-tests.js test/acceptance/* diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md index badf0e9510376..e97d2b2d59a4d 100644 --- a/docs/api-reference/cli.md +++ b/docs/api-reference/cli.md @@ -48,6 +48,14 @@ NODE_OPTIONS='--inspect' next The first load is colored green, yellow, or red. Aim for green for performant applications. +You can enable production profiling for React with the `--profile` flag in `next build`. This requires Next.js 9.5: + +```bash +next build --profile +``` + +After that, you can use the profiler in the same way as you would in development. + ## Development `next dev` starts the application in development mode with hot-code reloading, error reporting, and more: diff --git a/docs/api-reference/next.config.js/runtime-configuration.md b/docs/api-reference/next.config.js/runtime-configuration.md index 4a4a2a5ace6e1..b52e3e8898560 100644 --- a/docs/api-reference/next.config.js/runtime-configuration.md +++ b/docs/api-reference/next.config.js/runtime-configuration.md @@ -6,8 +6,6 @@ description: Add client and server runtime configuration to your Next.js app. > Generally you'll want to use [build-time environment variables](/docs/api-reference/next.config.js/environment-variables.md) to provide your configuration. The reason for this is that runtime configuration adds rendering / initialization overhead and is incompatible with [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md). -> Runtime configuration is not available when using the [`serverless` target](/docs/api-reference/next.config.js/build-target.md#serverless-target). - To add runtime configuration to your app open `next.config.js` and add the `publicRuntimeConfig` and `serverRuntimeConfig` configs: ```js diff --git a/errors/invalid-external-rewrite.md b/errors/invalid-external-rewrite.md new file mode 100644 index 0000000000000..89d114af835bf --- /dev/null +++ b/errors/invalid-external-rewrite.md @@ -0,0 +1,13 @@ +# Invalid External Rewrite + +#### Why This Error Occurred + +A rewrite was defined with both `basePath: false` and an internal `destination`. Rewrites that capture urls outside of the `basePath` must route externally, as they are intended for proxying in the case of incremental adoption of Next.js in a project. + +#### Possible Ways to Fix It + +Look for any rewrite where `basePath` is `false` and make sure its `destination` starts with `http://` or `https://`. + +### Useful Links + +- [Rewrites section in Documentation](https://nextjs.org/docs/api-reference/next.config.js/rewrites) diff --git a/examples/blog-starter-typescript/components/alert.tsx b/examples/blog-starter-typescript/components/alert.tsx index 9e07d7736c30c..f804ff6192868 100644 --- a/examples/blog-starter-typescript/components/alert.tsx +++ b/examples/blog-starter-typescript/components/alert.tsx @@ -18,7 +18,7 @@ const Alert = ({ preview }: Props) => {
{preview ? ( <> - This is page is a preview.{' '} + This page is a preview.{' '} {preview ? ( <> - This is page is a preview.{' '} + This page is a preview.{' '} { - // xa is the access token, which can be retrieved through - // firebase.auth().currentUser.getIdToken() - const { uid, email, xa } = user - const userData = { - id: uid, - email, - token: xa, - } - cookie.set('auth', userData, { - expires: 1, - }) + const userData = mapUserData(user) + setUserCookie(userData) }, }, } diff --git a/examples/with-firebase-authentication/pages/index.js b/examples/with-firebase-authentication/pages/index.js index c137f2888e2d4..0434faa8a443d 100644 --- a/examples/with-firebase-authentication/pages/index.js +++ b/examples/with-firebase-authentication/pages/index.js @@ -51,7 +51,7 @@ const Index = () => {
{error &&
Failed to fetch food!
} - {data ? ( + {data && !error ? (
Your favorite food is {data.food}.
) : (
Loading...
diff --git a/examples/with-firebase-authentication/utils/auth/mapUserData.js b/examples/with-firebase-authentication/utils/auth/mapUserData.js new file mode 100644 index 0000000000000..8036456b72bb6 --- /dev/null +++ b/examples/with-firebase-authentication/utils/auth/mapUserData.js @@ -0,0 +1,8 @@ +export const mapUserData = (user) => { + const { uid, email, xa } = user + return { + id: uid, + email, + token: xa, + } +} diff --git a/examples/with-firebase-authentication/utils/auth/useUser.js b/examples/with-firebase-authentication/utils/auth/useUser.js index 48c58d3cbdf46..cbd58d301e342 100644 --- a/examples/with-firebase-authentication/utils/auth/useUser.js +++ b/examples/with-firebase-authentication/utils/auth/useUser.js @@ -1,9 +1,14 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/router' -import cookies from 'js-cookie' import firebase from 'firebase/app' import 'firebase/auth' import initFirebase from '../auth/initFirebase' +import { + removeUserCookie, + setUserCookie, + getUserFromCookie, +} from './userCookies' +import { mapUserData } from './mapUserData' initFirebase() @@ -17,8 +22,6 @@ const useUser = () => { .signOut() .then(() => { // Sign-out successful. - cookies.remove('auth') - setUser() router.push('/auth') }) .catch((e) => { @@ -27,12 +30,26 @@ const useUser = () => { } useEffect(() => { - const cookie = cookies.get('auth') - if (!cookie) { + // Firebase updates the id token every hour, this + // makes sure the react state and the cookie are + // both kept up to date + firebase.auth().onIdTokenChanged((user) => { + if (user) { + const userData = mapUserData(user) + setUserCookie(userData) + setUser(userData) + } else { + removeUserCookie() + setUser() + } + }) + + const userFromCookie = getUserFromCookie() + if (!userFromCookie) { router.push('/') return } - setUser(JSON.parse(cookie)) + setUser(userFromCookie) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) diff --git a/examples/with-firebase-authentication/utils/auth/userCookies.js b/examples/with-firebase-authentication/utils/auth/userCookies.js new file mode 100644 index 0000000000000..776f02ff46310 --- /dev/null +++ b/examples/with-firebase-authentication/utils/auth/userCookies.js @@ -0,0 +1,19 @@ +import cookies from 'js-cookie' + +export const getUserFromCookie = () => { + const cookie = cookies.get('auth') + if (!cookie) { + return + } + return JSON.parse(cookie) +} + +export const setUserCookie = (user) => { + cookies.set('auth', user, { + // firebase id tokens expire in one hour + // set cookie expiry to match + expires: 1 / 24, + }) +} + +export const removeUserCookie = () => cookies.remove('auth') diff --git a/examples/with-firebase/.env.example b/examples/with-firebase/.env.example deleted file mode 100644 index 266032b8b8e96..0000000000000 --- a/examples/with-firebase/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# TODO. Fill in with Firebase Config -FIREBASE_API_KEY= -FIREBASE_AUTH_DOMAIN= -FIREBASE_DATABASE_URL= -FIREBASE_PROJECT_ID= -FIREBASE_STORAGE_BUCKET= -FIREBASE_MESSAGING_SENDER_ID= -FIREBASE_APP_ID= \ No newline at end of file diff --git a/examples/with-firebase/.env.local.example b/examples/with-firebase/.env.local.example new file mode 100644 index 0000000000000..7e1184d4a4215 --- /dev/null +++ b/examples/with-firebase/.env.local.example @@ -0,0 +1,8 @@ +# TODO. Fill in with Firebase Config +NEXT_PUBLIC_FIREBASE_API_KEY= +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= +NEXT_PUBLIC_FIREBASE_DATABASE_URL= +NEXT_PUBLIC_FIREBASE_PROJECT_ID= +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= +NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= +NEXT_PUBLIC_FIREBASE_APP_ID= \ No newline at end of file diff --git a/examples/with-firebase/README.md b/examples/with-firebase/README.md index 96419e36e9124..a34f4e313c173 100644 --- a/examples/with-firebase/README.md +++ b/examples/with-firebase/README.md @@ -1,4 +1,4 @@ -# With Firebase +# Firebase Example This is a simple set up for Firebase for client side applications. @@ -14,8 +14,6 @@ Deploy the example using [Vercel](https://vercel.com): ## How to use -### Using `create-next-app` - 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 @@ -24,34 +22,23 @@ npx create-next-app --example with-firebase with-firebase-app yarn create next-app --example with-firebase with-firebase-app ``` -### Download manually +## Configuration -Download the example: +1. [Create a Firebase project](https://console.firebase.google.com/u/0/) and add a new app to it. +2. Create a `.env.local` file and copy the contents of `.env.local.example` into it: ```bash -curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-firebase -cd with-firebase +cp .env.local.example .env.local ``` -### Configuration +3. Set each variable on `.env.local` with your Firebase Configuration (found in "Project settings"). -1. [Create a Firebase project](https://console.firebase.google.com/u/0/) and add a new app to it. -2. Create a `.env` file and copy the contents of `.env.example` into it: +## Deploy on Vercel -```bash -cp .env.example .env -``` +You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). -3. Set each variable on `.env` with your Firebase Configuration (found in "Project settings"). +### Deploy Your Local Project -Install it and run: - -```bash -npm install -npm run dev -# or -yarn -yarn dev -``` +To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example). -Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). +**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. diff --git a/examples/with-firebase/firebase/clientApp.js b/examples/with-firebase/firebase/clientApp.js index 32036c70c7760..7c7f8f7099898 100644 --- a/examples/with-firebase/firebase/clientApp.js +++ b/examples/with-firebase/firebase/clientApp.js @@ -5,13 +5,13 @@ import 'firebase/storage' // If you need it import 'firebase/analytics' // If you need it const clientCredentials = { - apiKey: process.env.FIREBASE_API_KEY, - authDomain: process.env.FIREBASE_AUTH_DOMAIN, - databaseURL: process.env.FIREBASE_DATABASE_URL, - projectId: process.env.FIREBASE_PROJECT_ID, - storageBucket: process.env.FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.FIREBASE_APP_ID, + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL, + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, } // Check that `window` is in scope for the analytics module! diff --git a/examples/with-firebase/next.config.js b/examples/with-firebase/next.config.js deleted file mode 100644 index 7bd8ac5a27654..0000000000000 --- a/examples/with-firebase/next.config.js +++ /dev/null @@ -1,14 +0,0 @@ -// On production, variables are set with `now secrets`. On development, they use the .env file -require('dotenv').config() - -module.exports = { - env: { - FIREBASE_API_KEY: process.env.FIREBASE_API_KEY, - FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN, - FIREBASE_DATABASE_URL: process.env.FIREBASE_DATABASE_URL, - FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID, - FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET, - FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID, - FIREBASE_APP_ID: process.env.FIREBASE_APP_ID, - }, -} diff --git a/examples/with-firebase/package.json b/examples/with-firebase/package.json index 828fa643cbe29..b5ff633b4e95f 100644 --- a/examples/with-firebase/package.json +++ b/examples/with-firebase/package.json @@ -7,12 +7,10 @@ "start": "next start" }, "dependencies": { - "firebase": "7.11.0", + "firebase": "7.17.0", "next": "latest", "react": "^16.13.0", "react-dom": "^16.13.0" }, - "devDependencies": { - "dotenv": "8.2.0" - } + "license": "MIT" } diff --git a/examples/with-glamor/pages/_document.js b/examples/with-glamor/pages/_document.js index 83dadc4a9d030..4bd79e43d38cc 100644 --- a/examples/with-glamor/pages/_document.js +++ b/examples/with-glamor/pages/_document.js @@ -3,7 +3,7 @@ import { renderStatic } from 'glamor/server' class MyDocument extends Document { static async getInitialProps({ renderPage }) { - const page = renderPage() + const page = await renderPage() const { css, ids } = renderStatic(() => page.html || page.errorHtml) return { ...page, css, ids } } diff --git a/examples/with-goober/pages/_document.js b/examples/with-goober/pages/_document.js index 505389b1ee106..bbd38e55885d3 100644 --- a/examples/with-goober/pages/_document.js +++ b/examples/with-goober/pages/_document.js @@ -2,9 +2,8 @@ import Document, { Head, Main, NextScript } from 'next/document' import { extractCss } from 'goober' export default class MyDocument extends Document { - static getInitialProps({ renderPage }) { - const page = renderPage() - + static async getInitialProps({ renderPage }) { + const page = await renderPage() // Extrach the css for each page render const css = extractCss() return { ...page, css } diff --git a/examples/with-mongodb-mongoose/components/Form.js b/examples/with-mongodb-mongoose/components/Form.js index 0336c5fb5c8c2..968df84a86ba6 100644 --- a/examples/with-mongodb-mongoose/components/Form.js +++ b/examples/with-mongodb-mongoose/components/Form.js @@ -33,6 +33,12 @@ const Form = ({ formId, petForm, forNewPet = true }) => { }, body: JSON.stringify(form), }) + + // Throw error with status code in case Fetch API req failed + if (!res.ok) { + throw new Error(res.status) + } + const { data } = await res.json() mutate(`/api/pets/${id}`, data, false) // Update the local data without a revalidation @@ -45,7 +51,7 @@ const Form = ({ formId, petForm, forNewPet = true }) => { /* The POST method adds a new entry in the mongodb database. */ const postData = async (form) => { try { - await fetch('/api/pets', { + const res = await fetch('/api/pets', { method: 'POST', headers: { Accept: contentType, @@ -53,6 +59,12 @@ const Form = ({ formId, petForm, forNewPet = true }) => { }, body: JSON.stringify(form), }) + + // Throw error with status code in case Fetch API req failed + if (!res.ok) { + throw new Error(res.status) + } + router.push('/') } catch (error) { setMessage('Failed to add pet') diff --git a/examples/with-react-md-typescript/.gitignore b/examples/with-react-md-typescript/.gitignore new file mode 100644 index 0000000000000..1437c53f70bc2 --- /dev/null +++ b/examples/with-react-md-typescript/.gitignore @@ -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 diff --git a/examples/with-react-md-typescript/README.md b/examples/with-react-md-typescript/README.md new file mode 100644 index 0000000000000..e119134b43257 --- /dev/null +++ b/examples/with-react-md-typescript/README.md @@ -0,0 +1,38 @@ +# ReactMD, Next.js, and Typescript Example + +This example sets up a simple [ReactMD](https://github.com/mlaursen/react-md), Next.js, and Typescript +app featuring: + +- [\_variables.scss](./styles/_variables.scss) to override the default + `react-md` theme and feature toggles +- [app.scss](./styles/app.scss) global styles that conditionally apply the dark theme + based on the user's OS preferences +- a custom [\_app.tsx](./pages/_app.tsx) that uses a persistent layout +- a reusable [Layout.tsx](./components/Layout/Layout.tsx) that: + - updates all the icons to use `SVGIcon`s instead of `FontIcon`s + - initializes the `Layout` component from `react-md` with navigation items + +For more information about ReactMD's features, styling, components, and API, check out +the [main documentation](https://react-md.dev). You can also view the +[documentation site's source code](https://github.com/mlaursen/react-md/tree/master/packages/documentation) +for a more complex example of using ReactMD + Next.js. + +## Deploy your own + +Deploy the example using [Vercel](https://vercel.com/now): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/with-react-md-typescript) + +## How to use + +### Using `create-next-app` + +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 with-react-md-typescript with-react-md-typescript-app +# or +yarn create next-app --example with-react-md-typescript with-react-md-typescript-app +``` + +Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-react-md-typescript/components/Layout/Layout.tsx b/examples/with-react-md-typescript/components/Layout/Layout.tsx new file mode 100644 index 0000000000000..5cc23c065c3ef --- /dev/null +++ b/examples/with-react-md-typescript/components/Layout/Layout.tsx @@ -0,0 +1,64 @@ +import React, { ReactElement, ReactNode } from 'react' +import { useRouter } from 'next/router' +import { + Layout as RMDLayout, + Configuration, + ConfigurableIcons, + useLayoutNavigation, + ArrowDropDownSVGIcon, + CheckBoxSVGIcon, + FileDownloadSVGIcon, + KeyboardArrowDownSVGIcon, + KeyboardArrowLeftSVGIcon, + KeyboardArrowRightSVGIcon, + MenuSVGIcon, + NotificationsSVGIcon, + RadioButtonCheckedSVGIcon, + RemoveRedEyeSVGIcon, + ArrowUpwardSVGIcon, + CheckSVGIcon, +} from 'react-md' + +import LinkUnstyled from '../LinkUnstyled' +import navItems from './navItems' + +const icons: ConfigurableIcons = { + back: , + checkbox: , + dropdown: , + download: , + expander: , + forward: , + menu: , + notification: , + radio: , + password: , + selected: , + sort: , +} + +interface LayoutProps { + children: ReactNode +} + +// Check out the documentation for Configuring your Layout for more information: +// - https://react-md.dev/guides/configuring-your-layout +export default function Layout({ children }: LayoutProps): ReactElement { + const { pathname } = useRouter() + + return ( + + + {children} + + + ) +} diff --git a/examples/with-react-md-typescript/components/Layout/index.ts b/examples/with-react-md-typescript/components/Layout/index.ts new file mode 100644 index 0000000000000..0e2737eeea41e --- /dev/null +++ b/examples/with-react-md-typescript/components/Layout/index.ts @@ -0,0 +1 @@ +export { default } from './Layout' diff --git a/examples/with-react-md-typescript/components/Layout/navItems.tsx b/examples/with-react-md-typescript/components/Layout/navItems.tsx new file mode 100644 index 0000000000000..4302375ae55a9 --- /dev/null +++ b/examples/with-react-md-typescript/components/Layout/navItems.tsx @@ -0,0 +1,35 @@ +import React, { ReactNode } from 'react' +import { + LayoutNavigationTree, + LayoutNavigationItem, + HomeSVGIcon, + TvSVGIcon, +} from 'react-md' + +/** + * Note: The `parentId` **must** be defaulted to `null` for the navigation tree + * to render correctly since this uses the @react-md/tree package behind the + * scenes. Each item that has a `parentId` set to `null` will appear at the root + * level of your navigation tree. + */ +function createRoute( + pathname: string, + children: string, + leftAddon: ReactNode | undefined, + parentId: string | null = null +): LayoutNavigationItem { + return { + itemId: pathname, + parentId, + href: pathname, + children, + leftAddon, + } +} + +const navItems: LayoutNavigationTree = { + '/': createRoute('/', 'Home', ), + '/route-1': createRoute('/route-1', 'Route 1', ), +} + +export default navItems diff --git a/examples/with-react-md-typescript/components/LinkUnstyled.tsx b/examples/with-react-md-typescript/components/LinkUnstyled.tsx new file mode 100644 index 0000000000000..5cfc04aff97f9 --- /dev/null +++ b/examples/with-react-md-typescript/components/LinkUnstyled.tsx @@ -0,0 +1,37 @@ +import React, { ReactElement, AnchorHTMLAttributes } from 'react' +import Link, { LinkProps } from 'next/link' + +export interface LinkUnstyledProps + extends Omit, + Omit, 'onError' | 'href'> {} + +export default function LinkUnstyled({ + as, + href, + scroll, + shallow, + replace, + children, + ...props +}: LinkUnstyledProps): ReactElement { + if (typeof href === 'string' && href.startsWith('http')) { + // external links + return ( +
+ {children} + + ) + } + + return ( + + {children} + + ) +} diff --git a/examples/with-react-md-typescript/next-env.d.ts b/examples/with-react-md-typescript/next-env.d.ts new file mode 100644 index 0000000000000..7b7aa2c7727d8 --- /dev/null +++ b/examples/with-react-md-typescript/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/with-react-md-typescript/package.json b/examples/with-react-md-typescript/package.json new file mode 100644 index 0000000000000..861df5aa42a2f --- /dev/null +++ b/examples/with-react-md-typescript/package.json @@ -0,0 +1,23 @@ +{ + "name": "with-react-md-typescript", + "version": "0.1.0", + "private": true, + "license": "ISC", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "16.13.1", + "react-dom": "16.13.1", + "react-md": "^2.1.1" + }, + "devDependencies": { + "@types/node": "^14.0.27", + "@types/react": "^16.9.43", + "node-sass": "^4.14.1", + "typescript": "^3.9.7" + } +} diff --git a/examples/with-react-md-typescript/pages/_app.tsx b/examples/with-react-md-typescript/pages/_app.tsx new file mode 100644 index 0000000000000..d267a5631fe3b --- /dev/null +++ b/examples/with-react-md-typescript/pages/_app.tsx @@ -0,0 +1,18 @@ +import React, { ReactElement } from 'react' +import Head from 'next/head' +import { AppProps } from 'next/app' + +import Layout from '../components/Layout' + +import '../styles/app.scss' + +export default function App({ Component, pageProps }: AppProps): ReactElement { + return ( + + + react-md with next.js + + + + ) +} diff --git a/examples/with-react-md-typescript/pages/index.tsx b/examples/with-react-md-typescript/pages/index.tsx new file mode 100644 index 0000000000000..cf2aa3d895c7b --- /dev/null +++ b/examples/with-react-md-typescript/pages/index.tsx @@ -0,0 +1,10 @@ +import React, { ReactElement } from 'react' +import { Text, TextContainer } from 'react-md' + +export default function Home(): ReactElement { + return ( + + Hello, world! + + ) +} diff --git a/examples/with-react-md-typescript/pages/route-1.tsx b/examples/with-react-md-typescript/pages/route-1.tsx new file mode 100644 index 0000000000000..16538c8b0532f --- /dev/null +++ b/examples/with-react-md-typescript/pages/route-1.tsx @@ -0,0 +1,10 @@ +import React, { ReactElement } from 'react' +import { TextContainer, Text } from 'react-md' + +export default function Route1(): ReactElement { + return ( + + Route 1 + + ) +} diff --git a/examples/with-react-md-typescript/styles/_variables.scss b/examples/with-react-md-typescript/styles/_variables.scss new file mode 100644 index 0000000000000..3cc7d0fc3aba0 --- /dev/null +++ b/examples/with-react-md-typescript/styles/_variables.scss @@ -0,0 +1,13 @@ +@import '~@react-md/theme/dist/color-palette'; + +// I'm bad with colors... update these variables as needed for +// your app +$rmd-theme-primary: $rmd-purple-500; +$rmd-theme-secondary: $rmd-pink-a-200; +$rmd-theme-light: true; + +$rmd-theme-dark-elevation: 'prefers-color-scheme'; +$rmd-utils-auto-dense: false; + +// any other react-md overrides or "global" variables used +// in your app diff --git a/examples/with-react-md-typescript/styles/app.scss b/examples/with-react-md-typescript/styles/app.scss new file mode 100644 index 0000000000000..c5357c17f67fd --- /dev/null +++ b/examples/with-react-md-typescript/styles/app.scss @@ -0,0 +1,11 @@ +// import react-md variable overrides +@import './variables'; + +// generate all react-md styles with the custom theme +@import 'react-md/dist/styles'; + +@media (prefers-color-scheme: dark) { + :root { + @include rmd-theme-dark; + } +} diff --git a/examples/with-react-md-typescript/tsconfig.json b/examples/with-react-md-typescript/tsconfig.json new file mode 100644 index 0000000000000..c65399cb28e5d --- /dev/null +++ b/examples/with-react-md-typescript/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowJs": true, + "alwaysStrict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "preserve", + "lib": ["dom", "es2017"], + "module": "esnext", + "moduleResolution": "node", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext" + }, + "exclude": ["node_modules"], + "include": ["**/*.ts", "**/*.tsx"] +} diff --git a/examples/with-react-md/README.md b/examples/with-react-md/README.md index 2230fdcb3cdb2..7a2f823d1e48b 100644 --- a/examples/with-react-md/README.md +++ b/examples/with-react-md/README.md @@ -1,14 +1,26 @@ -# Example app with react-md - -This example features how you use [react-md](https://react-md.dev/) (React Material Design) with Next.js. - -I recommend reading [layout-component](../layout-component) example next to learn how to reuse the layout across the pages. - -![Screenshot](https://cloud.githubusercontent.com/assets/304265/22472564/b2e04ff0-e7de-11e6-921e-d0c9833ac805.png) +# ReactMD and Next.js Example + +This example sets up a simple [ReactMD](https://github.com/mlaursen/react-md) and Next.js +app featuring: + +- [\_variables.scss](./styles/_variables.scss) to override the default + `react-md` theme and feature toggles +- [app.scss](./styles/app.scss) global styles that conditionally apply the dark theme + based on the user's OS preferences +- a custom [\_app.jsx](./pages/_app.jsx) that uses a persistent layout +- a reusable [Layout.jsx](./components/Layout/Layout.jsx) that: + - updates all the icons to use `SVGIcon`s instead of `FontIcon`s + - initializes the `Layout` component from `react-md` with navigation items + +For more information about ReactMD's features, styling, components, and API, check out +the [main documentation](https://react-md.dev). You can also view the +[documentation site's source code](https://github.com/mlaursen/react-md/tree/master/packages/documentation) +for a more complex example of using ReactMD + Next.js or the [with-react-md-typescript](../with-react-md-typescript) +example for Typescript support. ## Deploy your own -Deploy the example using [Vercel](https://vercel.com): +Deploy the example using [Vercel](https://vercel.com/now): [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/vercel/next.js/tree/canary/examples/with-react-md) @@ -24,25 +36,4 @@ npx create-next-app --example with-react-md with-react-md-app yarn create next-app --example with-react-md with-react-md-app ``` -### Download manually - -Download the example: - -```bash -curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-react-md -cd with-react-md -``` - -Install it and run: - -```bash -npm install -ln -f -s ../node_modules/react-md/dist/react-md.light_blue-yellow.min.css public/react-md.light_blue-yellow.min.css -npm run dev -# or -yarn -ln -f -s ../node_modules/react-md/dist/react-md.light_blue-yellow.min.css public/react-md.light_blue-yellow.min.css -yarn dev -``` - Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-react-md/components/Layout/Layout.jsx b/examples/with-react-md/components/Layout/Layout.jsx new file mode 100644 index 0000000000000..77f8db5ecb7d1 --- /dev/null +++ b/examples/with-react-md/components/Layout/Layout.jsx @@ -0,0 +1,59 @@ +import React from 'react' +import { useRouter } from 'next/router' +import { + Layout as RMDLayout, + Configuration, + useLayoutNavigation, + ArrowDropDownSVGIcon, + CheckBoxSVGIcon, + FileDownloadSVGIcon, + KeyboardArrowDownSVGIcon, + KeyboardArrowLeftSVGIcon, + KeyboardArrowRightSVGIcon, + MenuSVGIcon, + NotificationsSVGIcon, + RadioButtonCheckedSVGIcon, + RemoveRedEyeSVGIcon, + ArrowUpwardSVGIcon, + CheckSVGIcon, +} from 'react-md' + +import LinkUnstyled from '../LinkUnstyled' +import navItems from './navItems' + +const icons = { + back: , + checkbox: , + dropdown: , + download: , + expander: , + forward: , + menu: , + notification: , + radio: , + password: , + selected: , + sort: , +} + +// Check out the documentation for Configuring your Layout for more information: +// - https://react-md.dev/guides/configuring-your-layout +export default function Layout({ children }) { + const { pathname } = useRouter() + + return ( + + + {children} + + + ) +} diff --git a/examples/with-react-md/components/Layout/index.js b/examples/with-react-md/components/Layout/index.js new file mode 100644 index 0000000000000..0e2737eeea41e --- /dev/null +++ b/examples/with-react-md/components/Layout/index.js @@ -0,0 +1 @@ +export { default } from './Layout' diff --git a/examples/with-react-md/components/Layout/navItems.jsx b/examples/with-react-md/components/Layout/navItems.jsx new file mode 100644 index 0000000000000..1027dd2a66a80 --- /dev/null +++ b/examples/with-react-md/components/Layout/navItems.jsx @@ -0,0 +1,25 @@ +import React from 'react' +import { HomeSVGIcon, TvSVGIcon } from 'react-md' + +/** + * Note: The `parentId` **must** be defaulted to `null` for the navigation tree + * to render correctly since this uses the @react-md/tree package behind the + * scenes. Each item that has a `parentId` set to `null` will appear at the root + * level of your navigation tree. + */ +function createRoute(pathname, children, leftAddon, parentId = null) { + return { + itemId: pathname, + parentId, + href: pathname, + children, + leftAddon, + } +} + +const navItems = { + '/': createRoute('/', 'Home', ), + '/route-1': createRoute('/route-1', 'Route 1', ), +} + +export default navItems diff --git a/examples/with-react-md/components/LinkUnstyled.jsx b/examples/with-react-md/components/LinkUnstyled.jsx new file mode 100644 index 0000000000000..dd777a75a2728 --- /dev/null +++ b/examples/with-react-md/components/LinkUnstyled.jsx @@ -0,0 +1,33 @@ +import React from 'react' +import Link from 'next/link' + +export default function LinkUnstyled({ + as, + href, + scroll, + shallow, + replace, + children, + ...props +}) { + if (typeof href === 'string' && href.startsWith('http')) { + // external links + return ( + + {children} + + ) + } + + return ( + + {children} + + ) +} diff --git a/examples/with-react-md/package.json b/examples/with-react-md/package.json index 6f6f5ef6b90e4..2ff86c3dec5f8 100644 --- a/examples/with-react-md/package.json +++ b/examples/with-react-md/package.json @@ -1,6 +1,8 @@ { "name": "with-react-md", "version": "1.0.0", + "private": true, + "license": "ISC", "scripts": { "dev": "next", "build": "next build", @@ -8,11 +10,11 @@ }, "dependencies": { "next": "latest", - "react": "^16.7.0", - "react-addons-css-transition-group": "^16.0.0", - "react-addons-transition-group": "^16.0.0", - "react-dom": "^16.7.0", - "react-md": "^1.0.1" + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-md": "^2.1.1" }, - "license": "ISC" + "devDependencies": { + "node-sass": "^4.14.1" + } } diff --git a/examples/with-react-md/pages/_app.js b/examples/with-react-md/pages/_app.js deleted file mode 100644 index dc54b936a17f3..0000000000000 --- a/examples/with-react-md/pages/_app.js +++ /dev/null @@ -1,5 +0,0 @@ -import 'react-md/dist/react-md.light_blue-yellow.min.css' - -export default function MyApp({ Component, pageProps }) { - return -} diff --git a/examples/with-react-md/pages/_app.jsx b/examples/with-react-md/pages/_app.jsx new file mode 100644 index 0000000000000..30774fd21ae00 --- /dev/null +++ b/examples/with-react-md/pages/_app.jsx @@ -0,0 +1,17 @@ +import React from 'react' +import Head from 'next/head' + +import Layout from '../components/Layout' + +import '../styles/app.scss' + +export default function App({ Component, pageProps }) { + return ( + + + react-md with next.js + + + + ) +} diff --git a/examples/with-react-md/pages/index.js b/examples/with-react-md/pages/index.js deleted file mode 100644 index 6d22d1c3bb8c5..0000000000000 --- a/examples/with-react-md/pages/index.js +++ /dev/null @@ -1,116 +0,0 @@ -import Head from 'next/head' -import Link from 'next/link' - -import { PureComponent } from 'react' - -import Avatar from 'react-md/lib/Avatars' -import Button from 'react-md/lib/Buttons/Button' -import FontIcon from 'react-md/lib/FontIcons' -import ListItem from 'react-md/lib/Lists/ListItem' -import NavigationDrawer from 'react-md/lib/NavigationDrawers' -import SelectField from 'react-md/lib/SelectFields' - -const avatarSrc = - 'https://cloud.githubusercontent.com/assets/13041/19686250/971bf7f8-9ac0-11e6-975c-188defd82df1.png' - -const drawerHeaderChildren = [ - , - , -] - -class NavigationLink extends PureComponent { - // NOTE: Don't try using Stateless (function) component here. `ref` is - // required by React-MD/AccessibleFakeButton, but Stateless components - // don't have one by design: - // https://github.com/facebook/react/issues/4936 - render() { - const { href, as, children, ..._props } = this.props - return ( - - ) - } -} - -export default function Home() { - const closeButton = ( - - ) - - return ( -
- - - - - inbox} - tileClassName="md-list-tile--mini" - primaryText={'Root'} - />, - star} - tileClassName="md-list-tile--mini" - primaryText={'404 page'} - />, - ]} - contentClassName="md-grid" - drawerHeaderChildren={drawerHeaderChildren} - mobileDrawerType={NavigationDrawer.DrawerTypes.TEMPORARY_MINI} - tabletDrawerType={NavigationDrawer.DrawerTypes.PERSISTENT_MINI} - desktopDrawerType={NavigationDrawer.DrawerTypes.PERSISTENT_MINI} - toolbarTitle="Hello, World!" - toolbarActions={closeButton} - > -

Hello Next.js!

-
-
- ) -} diff --git a/examples/with-react-md/pages/index.jsx b/examples/with-react-md/pages/index.jsx new file mode 100644 index 0000000000000..5149adaef8da5 --- /dev/null +++ b/examples/with-react-md/pages/index.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import { Text, TextContainer } from 'react-md' + +export default function Home() { + return ( + + Hello, world! + + ) +} diff --git a/examples/with-react-md/pages/route-1.jsx b/examples/with-react-md/pages/route-1.jsx new file mode 100644 index 0000000000000..3d5d70ef8eb03 --- /dev/null +++ b/examples/with-react-md/pages/route-1.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import { TextContainer, Text } from 'react-md' + +export default function Route1() { + return ( + + Route 1 + + ) +} diff --git a/examples/with-react-md/styles/_variables.scss b/examples/with-react-md/styles/_variables.scss new file mode 100644 index 0000000000000..3cc7d0fc3aba0 --- /dev/null +++ b/examples/with-react-md/styles/_variables.scss @@ -0,0 +1,13 @@ +@import '~@react-md/theme/dist/color-palette'; + +// I'm bad with colors... update these variables as needed for +// your app +$rmd-theme-primary: $rmd-purple-500; +$rmd-theme-secondary: $rmd-pink-a-200; +$rmd-theme-light: true; + +$rmd-theme-dark-elevation: 'prefers-color-scheme'; +$rmd-utils-auto-dense: false; + +// any other react-md overrides or "global" variables used +// in your app diff --git a/examples/with-react-md/styles/app.scss b/examples/with-react-md/styles/app.scss new file mode 100644 index 0000000000000..c5357c17f67fd --- /dev/null +++ b/examples/with-react-md/styles/app.scss @@ -0,0 +1,11 @@ +// import react-md variable overrides +@import './variables'; + +// generate all react-md styles with the custom theme +@import 'react-md/dist/styles'; + +@media (prefers-color-scheme: dark) { + :root { + @include rmd-theme-dark; + } +} diff --git a/examples/with-react-native-web/pages/_document.js b/examples/with-react-native-web/pages/_document.js index e55bc7962bfa8..6363c1820fc02 100644 --- a/examples/with-react-native-web/pages/_document.js +++ b/examples/with-react-native-web/pages/_document.js @@ -15,7 +15,7 @@ export default class MyDocument extends Document { static async getInitialProps({ renderPage }) { AppRegistry.registerComponent(config.name, () => Main) const { getStyleElement } = AppRegistry.getApplication(config.name) - const page = renderPage() + const page = await renderPage() const styles = [