diff --git a/examples/pigment-css-nextjs-ts/.gitignore b/examples/pigment-css-nextjs-ts/.gitignore new file mode 100644 index 00000000000000..28c8a5adb7c034 --- /dev/null +++ b/examples/pigment-css-nextjs-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/pigment-css-nextjs-ts/README.md b/examples/pigment-css-nextjs-ts/README.md new file mode 100644 index 00000000000000..3449ebdbd149ec --- /dev/null +++ b/examples/pigment-css-nextjs-ts/README.md @@ -0,0 +1,38 @@ +# Pigment CSS - Next.js App Router with TypeScript example project + +This is a [Next.js](https://nextjs.org/) project bootstrapped using [`create-next-app`](https://github.com/vercel/next.js/tree/HEAD/packages/create-next-app), with TypeScript and MUI's zero-runtime CSS-in-JS installed. + +## How to use + +Download the example [or clone the repo](https://github.com/mui/material-ui): + + + +```bash +curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/pigment-css-nextjs-ts +cd pigment-css-nextjs-ts +``` + +Install it and run: + +```bash +npm install +npm run dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +or: + + + +[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/pigment-css-nextjs-ts) + +[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/github/mui/material-ui/tree/master/examples/pigment-css-nextjs-ts) + +## Learn more + +To learn more about this example: + +- [Pigment CSS documentation](https://github.com/mui/material-ui/blob/master/packages/pigment-react/README.md) - learn more about Pigment CSS features and APIs. +- [Next.js documentation](https://nextjs.org/docs) - learn about Next.js features and APIs. diff --git a/examples/pigment-css-nextjs-ts/next-env.d.ts b/examples/pigment-css-nextjs-ts/next-env.d.ts new file mode 100644 index 00000000000000..4f11a03dc6cc37 --- /dev/null +++ b/examples/pigment-css-nextjs-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/pigment-css-nextjs-ts/next.config.js b/examples/pigment-css-nextjs-ts/next.config.js new file mode 100644 index 00000000000000..2196cab8c75dda --- /dev/null +++ b/examples/pigment-css-nextjs-ts/next.config.js @@ -0,0 +1,28 @@ +const { withPigment, extendTheme } = require('@pigment-css/nextjs-plugin'); + +// To learn more about theming, visit https://github.com/mui/material-ui/blob/master/packages/zero-runtime/README.md#theming +const theme = extendTheme({ + colorSchemes: { + light: { + palette: { + background: '0 0% 100%', + foreground: '240 10% 3.9%', + primary: '240 5.9% 10%', + border: '240 5.9% 90%', + }, + }, + dark: { + palette: { + background: '240 10% 3.9%', + foreground: '0 0% 80%', + primary: '0 0% 98%', + border: '240 3.7% 15.9%', + }, + }, + }, +}); + +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = withPigment(nextConfig, { theme }); diff --git a/examples/pigment-css-nextjs-ts/package.json b/examples/pigment-css-nextjs-ts/package.json new file mode 100644 index 00000000000000..b2579629a61906 --- /dev/null +++ b/examples/pigment-css-nextjs-ts/package.json @@ -0,0 +1,27 @@ +{ + "name": "pigment-css-nextjs-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\" && pnpm update --latest" + }, + "dependencies": { + "@pigment-css/react": "latest", + "react": "latest", + "react-dom": "latest", + "next": "latest" + }, + "devDependencies": { + "@pigment-css/nextjs-plugin": "latest", + "@types/node": "latest", + "@types/react": "latest", + "@types/react-dom": "latest", + "eslint": "latest", + "eslint-config-next": "latest", + "typescript": "latest" + } +} diff --git a/examples/pigment-css-nextjs-ts/public/.gitkeep b/examples/pigment-css-nextjs-ts/public/.gitkeep new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/examples/pigment-css-nextjs-ts/src/app/favicon.ico b/examples/pigment-css-nextjs-ts/src/app/favicon.ico new file mode 100644 index 00000000000000..e19f48f591206d Binary files /dev/null and b/examples/pigment-css-nextjs-ts/src/app/favicon.ico differ diff --git a/examples/pigment-css-nextjs-ts/src/app/globals.css b/examples/pigment-css-nextjs-ts/src/app/globals.css new file mode 100644 index 00000000000000..3dd82369c1b57d --- /dev/null +++ b/examples/pigment-css-nextjs-ts/src/app/globals.css @@ -0,0 +1,10 @@ +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +a { + color: inherit; + text-decoration: none; +} diff --git a/examples/pigment-css-nextjs-ts/src/app/layout.tsx b/examples/pigment-css-nextjs-ts/src/app/layout.tsx new file mode 100644 index 00000000000000..fb3a3b8adb2db7 --- /dev/null +++ b/examples/pigment-css-nextjs-ts/src/app/layout.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import type { Metadata } from 'next'; +import '@pigment-css/react/styles.css'; +import { css } from '@pigment-css/react'; + +import './globals.css'; + +export const metadata: Metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +}; + +export default function RootLayout(props: { children: React.ReactNode }) { + return ( + + ({ + color: 'hsl(var(--palette-foreground))', + backgroundColor: 'hsl(var(--palette-background))', + fontFamily: + "system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'", + ...theme.applyStyles('dark', { + colorScheme: 'dark', + }), + }))} + > + {props.children} + + + ); +} diff --git a/examples/pigment-css-nextjs-ts/src/app/page.tsx b/examples/pigment-css-nextjs-ts/src/app/page.tsx new file mode 100644 index 00000000000000..08a5fc7e6cb3fe --- /dev/null +++ b/examples/pigment-css-nextjs-ts/src/app/page.tsx @@ -0,0 +1,204 @@ +import * as React from 'react'; +import { Kalnia, Josefin_Sans } from 'next/font/google'; +import { styled, css, keyframes } from '@pigment-css/react'; + +const kalnia = Kalnia({ subsets: ['latin'] }); +const josefin = Josefin_Sans({ subsets: ['latin'] }); + +const scale = keyframes({ + to: { scale: 'var(--s2)' }, +}); + +const Link = styled('a', { shouldForwardProp: (prop) => prop !== 'outlined' })<{ + outlined?: boolean; +}>(({ theme }) => ({ + fontSize: '1rem', + background: 'rgba(0 0 0 / 0.04)', + padding: '0.5rem 1rem', + letterSpacing: '1px', + borderRadius: '4px', + textAlign: 'center', + ...theme.applyStyles('dark', { + background: 'rgba(255 255 255 / 0.1)', + }), + variants: [ + { + props: { outlined: true }, + style: { + background: 'transparent', + color: `hsl(${theme.vars.palette.primary})`, + border: `1px solid hsl(${theme.vars.palette.border})`, + }, + }, + ], +})); + +const Bubble = styled('span')({ + height: 'var(--size, 100%)', + aspectRatio: '1', + background: 'radial-gradient(hsl(var(--h) 100% 70%) 25%, transparent 50%)', + position: 'absolute', + display: 'inline-block', + left: 'var(--x, 0)', + top: 'var(--y, 0)', + scale: '0', + translate: '-50% -50%', + mixBlendMode: 'multiply', + filter: 'blur(2px)', + animation: `${scale} var(--s, 2s) var(--d, 0s) infinite alternate`, +}); + +function randomBetween(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1) + min); +} +function generateBubbleVars() { + return ` + --x: ${randomBetween(10, 90)}%; + --y: ${randomBetween(15, 85)}%; + --h: ${randomBetween(0, 360)}; + --s2: ${randomBetween(2, 6)}; + --d: -${randomBetween(250, 400) / 1000}s; + --s: ${randomBetween(3, 6)}s; + `; +} + +export default function Home() { + return ( +
+

({ + fontSize: 'clamp(3rem, 1.9503rem + 4.4789vw, 6.25rem)', + fontWeight: 500, + textAlign: 'center', + position: 'relative', + display: 'flex', + alignItems: 'center', + color: '#888', + marginBottom: '1rem', + ...theme.applyStyles('dark', { color: '#fff' }), + }))}`} + > + Pigment CSS + ({ + position: 'absolute', + inset: '0', + background: 'white', + mixBlendMode: 'color-burn', + overflow: 'hidden', + pointerEvents: 'none', + ...theme.applyStyles('dark', { + mixBlendMode: 'darken', + filter: 'brightness(2)', + }), + }))} + > + + + + + + + + + + + +

+
+ CSS-in-JS library with static extraction +
+ +
*': { flex: 'auto' }, + })} + > + + Documentation + + + Roadmap + +
+
+ ); +} diff --git a/examples/pigment-css-nextjs-ts/src/augment.d.ts b/examples/pigment-css-nextjs-ts/src/augment.d.ts new file mode 100644 index 00000000000000..d10b46e01d4da0 --- /dev/null +++ b/examples/pigment-css-nextjs-ts/src/augment.d.ts @@ -0,0 +1,19 @@ +import type {} from '@pigment-css/react/theme'; +import type { ExtendTheme } from '@pigment-css/react'; + +declare module '@pigment-css/react/theme' { + export interface ThemeArgs { + theme: ExtendTheme<{ + colorScheme: 'light' | 'dark'; + tokens: { + palette: { + background: string; + foreground: string; + primary: string; + primaryForeground: string; + border: string; + }; + }; + }>; + } +} diff --git a/examples/pigment-css-nextjs-ts/tsconfig.json b/examples/pigment-css-nextjs-ts/tsconfig.json new file mode 100644 index 00000000000000..6a9c1a2e7a02a6 --- /dev/null +++ b/examples/pigment-css-nextjs-ts/tsconfig.json @@ -0,0 +1,29 @@ +{ + "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" + } + ], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/pigment-react/README.md b/packages/pigment-react/README.md index afca1fa7e9e461..358a92fc19ba1f 100644 --- a/packages/pigment-react/README.md +++ b/packages/pigment-react/README.md @@ -37,6 +37,17 @@ Pigment CSS is built on top of [WyW-in-JS](https://wyw-in-js.dev/), enabling us ### Next.js +#### Starter template + +Use the following commands to create a new Next.js project with Pigment CSS set up: + +```bash +curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/pigment-css-nextjs-ts +cd pigment-css-nextjs-ts +``` + +#### Manual installation + ```bash npm install @pigment-css/react npm install --save-dev @pigment-css/nextjs-plugin @@ -52,6 +63,26 @@ module.exports = withPigment({ }); ``` +Finally, import the stylesheet in the root `layout.tsx` file: + +```diff + import type { Metadata } from 'next'; ++import '@mui/zero-runtime/styles.css'; + + export const metadata: Metadata = { + title: 'Create Next App', + description: 'Generated by create next app', + }; + + export default function RootLayout(props: { children: React.ReactNode }) { + return ( + + {props.children} + + ); + } +``` + ### Vite ```bash @@ -72,6 +103,24 @@ export default defineConfig({ }); ``` +Finally, import the stylesheet in the root `main.tsx` file: + +```diff + import * as React from 'react'; + import { createRoot } from 'react-dom/client'; ++import '@mui/zero-runtime/styles.css'; + import App from './App'; + + const rootElement = document.getElementById('root'); + const root = createRoot(rootElement); + + root.render( + + + , + ); +``` + ## Basic usage > You must configure Pigment CSS with [Next.js](#nextjs) or [Vite](#vite) first. @@ -306,6 +355,52 @@ const Heading = styled('h1')<{ isError?: boolean }>({ }); ``` +### Creating animation keyframes + +Use the `keyframes` API to create reusable [animation keyframes](https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes): + +```js +import { keyframes } from '@mui/zero-runtime'; + +const fadeIn = keyframes` + from { + opacity: 0; + } + to { + opacity: 1; + } +`; + +function Example1() { + return
I am invisible
; +} +``` + +The call to the `keyframes` function will be replaced with a unique string that represents the CSS animation name. It can be used with `css` or `styled` too. + +```js +import { css, styled, keyframes } from '@mui/zero-runtime'; + +const fadeIn = keyframes(...); + +const Example2 = styled('div')({ + animation: `${fadeIn} 0.5s`, +}); + +function App() { + return ( + <> + +
+ + ) +} +``` + ### Theming Theming is an **optional** feature that lets you reuse the same values, such as colors, spacing, and typography, across your application. It is a plain object of any structure that you can define in your config file. @@ -454,6 +549,21 @@ function App() { } ``` +#### Styling based on color scheme + +The `extendTheme` utility attach a function called `applyStyles` to the theme object. It receives a color scheme as the first argument followed by a style object. It will return a proper CSS selector based on the theme configuration. + +```jsx +const Heading = styled('h1')(({ theme }) => ({ + color: theme.colors.primary, + fontSize: theme.spacing.unit * 4, + fontFamily: theme.typography.fontFamily, + ...theme.applyStyles('dark', { + color: theme.colors.primaryLight, + }), +})); +``` + #### CSS variables prefix You can add a prefix to the generated CSS variables by providing a `cssVarPrefix` option to the `extendTheme` utility: