diff --git a/examples/material-next-app-router-ts/.gitignore b/examples/material-next-app-router-ts/.gitignore new file mode 100644 index 00000000000000..28c8a5adb7c034 --- /dev/null +++ b/examples/material-next-app-router-ts/.gitignore @@ -0,0 +1,36 @@ +# 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* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +# next-env.d.ts diff --git a/examples/material-next-app-router-ts/README.md b/examples/material-next-app-router-ts/README.md new file mode 100644 index 00000000000000..53f57470584c06 --- /dev/null +++ b/examples/material-next-app-router-ts/README.md @@ -0,0 +1,31 @@ +# Material UI - Next.js App Router example in TypeScript + +## How to use + +Download the example [or clone the repo](https://github.com/mui/material-ui): + + + +```sh +curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/material-next-ts +cd material-next-app-router-ts +``` + +Install it and run: + +```sh +npm install +npm run dev +``` + +## The idea behind the example + +The project uses [Next.js](https://github.com/vercel/next.js), which is a framework for server-rendered React apps. +It includes `@mui/material` and its peer dependencies, including [Emotion](https://emotion.sh/docs/introduction), the default style engine in Material UI v5. If you prefer, you can [use styled-components instead](https://mui.com/material-ui/guides/interoperability/#styled-components). + +## What's next? + + + +You now have a working example project. +You can head back to the documentation, continuing browsing it from the [templates](https://mui.com/material-ui/getting-started/templates/) section. diff --git a/examples/material-next-app-router-ts/next-env.d.ts b/examples/material-next-app-router-ts/next-env.d.ts new file mode 100644 index 00000000000000..4f11a03dc6cc37 --- /dev/null +++ b/examples/material-next-app-router-ts/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/material-next-app-router-ts/next.config.js b/examples/material-next-app-router-ts/next.config.js new file mode 100644 index 00000000000000..78ff974e7e2f4e --- /dev/null +++ b/examples/material-next-app-router-ts/next.config.js @@ -0,0 +1,12 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + modularizeImports: { + '@mui/icons-material': { + transform: '@mui/icons-material/{{member}}', + }, + }, +}; + +module.exports = nextConfig; diff --git a/examples/material-next-app-router-ts/package.json b/examples/material-next-app-router-ts/package.json new file mode 100644 index 00000000000000..c1375cb7c48b24 --- /dev/null +++ b/examples/material-next-app-router-ts/package.json @@ -0,0 +1,30 @@ +{ + "name": "material-next-app-router-ts", + "version": "5.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "post-update": "echo \"codesandbox preview only, need an update\" && yarn upgrade --latest" + }, + "dependencies": { + "@emotion/cache": "latest", + "@emotion/react": "latest", + "@emotion/styled": "latest", + "@mui/icons-material": "latest", + "@mui/material": "latest", + "next": "latest", + "react": "latest", + "react-dom": "latest" + }, + "devDependencies": { + "@types/node": "latest", + "@types/react": "latest", + "@types/react-dom": "latest", + "eslint": "latest", + "eslint-config-next": "latest", + "typescript": "latest" + } +} diff --git a/examples/material-next-app-router-ts/public/.gitkeep b/examples/material-next-app-router-ts/public/.gitkeep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/examples/material-next-app-router-ts/src/app/about/page.tsx b/examples/material-next-app-router-ts/src/app/about/page.tsx new file mode 100644 index 00000000000000..96ffd6aaf99fb6 --- /dev/null +++ b/examples/material-next-app-router-ts/src/app/about/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import About from '@/layouts/About/About'; + +export default function AboutPage() { + return ; +} diff --git a/examples/material-next-app-router-ts/src/app/favicon.ico b/examples/material-next-app-router-ts/src/app/favicon.ico new file mode 100644 index 00000000000000..718d6fea4835ec Binary files /dev/null and b/examples/material-next-app-router-ts/src/app/favicon.ico differ diff --git a/examples/material-next-app-router-ts/src/app/fonts/fonts.ts b/examples/material-next-app-router-ts/src/app/fonts/fonts.ts new file mode 100644 index 00000000000000..3aed094a9f319b --- /dev/null +++ b/examples/material-next-app-router-ts/src/app/fonts/fonts.ts @@ -0,0 +1,12 @@ +// Fonts Example +import { Inter } from 'next/font/google'; + +const GoogleInterFont = Inter({ subsets: ['latin'] }); + +export default GoogleInterFont; + +// Local Fonts example +// more details here: https://nextjs.org/docs/app/building-your-application/optimizing/fonts#local-fonts +// import localFont from 'next/font/local'; +// const LocalFont = localFont({src: [{path: './path-of-font-file-regular.woff', weight: '400', style: 'normal'}], fallback: ['Arial', 'sans-serif']}) +// export default LocalFont; diff --git a/examples/material-next-app-router-ts/src/app/layout.tsx b/examples/material-next-app-router-ts/src/app/layout.tsx new file mode 100644 index 00000000000000..0bf9d7a47e0df0 --- /dev/null +++ b/examples/material-next-app-router-ts/src/app/layout.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import ThemeRegistry from '@/components/Theme/ThemeRegistry/ThemeRegistry'; + +export const metadata = { + title: 'Next App with MUI5', + description: 'next app with mui5', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/examples/material-next-app-router-ts/src/app/page.tsx b/examples/material-next-app-router-ts/src/app/page.tsx new file mode 100644 index 00000000000000..a91df75626a081 --- /dev/null +++ b/examples/material-next-app-router-ts/src/app/page.tsx @@ -0,0 +1,6 @@ +import * as React from 'react'; +import Home from '@/layouts/Home/Home'; + +export default function RootPage() { + return ; +} diff --git a/examples/material-next-app-router-ts/src/components/CopyRight/Copyright.tsx b/examples/material-next-app-router-ts/src/components/CopyRight/Copyright.tsx new file mode 100644 index 00000000000000..02dd5984cd85cb --- /dev/null +++ b/examples/material-next-app-router-ts/src/components/CopyRight/Copyright.tsx @@ -0,0 +1,17 @@ +'use client'; + +import * as React from 'react'; +import Typography from '@mui/material/Typography'; +import Link from '@mui/material/Link'; + +export default function Copyright() { + return ( + + {'Copyright © '} + + Your Website + + {new Date().getFullYear()}. + + ); +} diff --git a/examples/material-next-app-router-ts/src/components/ProTip/ProTip.tsx b/examples/material-next-app-router-ts/src/components/ProTip/ProTip.tsx new file mode 100644 index 00000000000000..746aeb8ce5a60e --- /dev/null +++ b/examples/material-next-app-router-ts/src/components/ProTip/ProTip.tsx @@ -0,0 +1,24 @@ +'use client'; + +import * as React from 'react'; +import Typography from '@mui/material/Typography'; +import Link from '@mui/material/Link'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; + +function LightBulbIcon(props: SvgIconProps) { + return ( + + + + ); +} + +export default function ProTip() { + return ( + + + Pro tip: See more templates in + the MUI documentation. + + ); +} diff --git a/examples/material-next-app-router-ts/src/components/Theme/ThemeRegistry/EmotionCache.tsx b/examples/material-next-app-router-ts/src/components/Theme/ThemeRegistry/EmotionCache.tsx new file mode 100644 index 00000000000000..3be27ae2e4cc74 --- /dev/null +++ b/examples/material-next-app-router-ts/src/components/Theme/ThemeRegistry/EmotionCache.tsx @@ -0,0 +1,69 @@ +'use client'; + +import * as React from 'react'; +import createCache from '@emotion/cache'; +import { useServerInsertedHTML } from 'next/navigation'; +import { CacheProvider as DefaultCacheProvider } from '@emotion/react'; +import type { EmotionCache, Options as OptionsOfCreateCache } from '@emotion/cache'; + +export type NextAppDirEmotionCacheProviderProps = { + /** This is the options passed to createCache() from 'import createCache from "@emotion/cache"' */ + options: Omit; + /** By default from 'import { CacheProvider } from "@emotion/react"' */ + CacheProvider?: (props: { + value: EmotionCache; + children: React.ReactNode; + }) => React.JSX.Element | null; + children: React.ReactNode; +}; + +// This implementation is taken from https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx +export function NextAppDirEmotionCacheProvider(props: NextAppDirEmotionCacheProviderProps) { + const { options, CacheProvider = DefaultCacheProvider, children } = props; + + const [{ cache, flush }] = React.useState(() => { + // eslint-disable-next-line @typescript-eslint/no-shadow + const cache = createCache(options); + cache.compat = true; + const prevInsert = cache.insert; + let inserted: string[] = []; + cache.insert = (...args) => { + const serialized = args[1]; + if (cache.inserted[serialized.name] === undefined) { + inserted.push(serialized.name); + } + return prevInsert(...args); + }; + // eslint-disable-next-line @typescript-eslint/no-shadow + const flush = () => { + const prevInserted = inserted; + inserted = []; + return prevInserted; + }; + return { cache, flush }; + }); + + useServerInsertedHTML(() => { + const names = flush(); + if (names.length === 0) { + return null; + } + let styles = ''; + // eslint-disable-next-line no-restricted-syntax + for (const name of names) { + styles += cache.inserted[name]; + } + return ( +