diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 9a66d8694..8980d81e5 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,5 +1,5 @@ { "packages": ["packages/*"], "sandboxes": ["pk1qjqpw67"], - "node": "12" + "node": "16" } diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..43217ab46 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space + +[*.{js,ts,tsx,json,yml}] +indent_size = 2 \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index 2ad384975..738ec7934 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,5 +4,8 @@ coverage/ node_modules/ stylis.min.js /demo/dist -/packages/site/build -flow-typed/ \ No newline at end of file +flow-typed/ + +# This is to prevent ESLint parsing errors in the website which is written in +# TypeScript. TODO: Reenable once the codebase is converted to TypeScript. +/site/ \ No newline at end of file diff --git a/.github/actions/ci-setup/action.yml b/.github/actions/ci-setup/action.yml index df442d272..29103e689 100644 --- a/.github/actions/ci-setup/action.yml +++ b/.github/actions/ci-setup/action.yml @@ -2,7 +2,7 @@ name: 'CI setup' runs: using: 'composite' steps: - - name: Set Node.js 16.x + - name: Setup Node.js 16.x uses: actions/setup-node@v3 with: node-version: 16.x diff --git a/.gitignore b/.gitignore index 23ea634a8..fba09572d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,12 +4,15 @@ dist/ node_modules/ *.log .idea -site/build package-lock.json .DS_Store .cache -public/ -!playgrounds/cra/public .env .vscode -.parcel-cache/ \ No newline at end of file +.parcel-cache/ +*.orig +*.tsbuildinfo + +# Website +site/out +.next \ No newline at end of file diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 48082f72f..000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -12 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cf1c6b2f..9b032b045 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,10 +5,6 @@ ## Installation -- (If using an M1 Mac) Install `vips` via Homebrew: `brew install vips`. - - This step can be removed from this document if we upgrade to the latest - version of Gatsby, which is compatible with sharp 0.28.0+ which does include - binaries for M1 Macs. - (If using Windows) Enable Developer Mode in the Windows settings app. On Windows 11, this can be done by searching the start menu for "Developer settings" and then enabling the Developer Mode toggle switch. - Run `yarn` in the repository's root directory to install everything you need for development. - Run `yarn build` in the root directory to build the modules. @@ -27,9 +23,9 @@ ## Documentation Website Development -- Run above installation steps and then -- Run `yarn start:site` to run a development server of gatsby. -- Run `yarn build:site` to create a build of the assets for the documentation website. +- Run above installation steps and then `cd` to the `site` directory. +- Run `yarn dev` to run the Next.js development server. +- Run `yarn build` to create a build of the assets for the documentation website. ## Changesets diff --git a/README.md b/README.md index 3738de531..a0b2b62d1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ ![@emotion/styled size](https://img.shields.io/bundlephobia/min/@emotion/styled.svg?label=@emotion/styled%20size) ![@emotion/styled gzip size](https://img.shields.io/bundlephobia/minzip/@emotion/styled.svg?label=@emotion/styled%20gzip%20size) [![slack](https://img.shields.io/badge/join-slack-green)](https://join.slack.com/t/emotion-slack/shared_invite/zt-rmtwsy74-2uvyFdz5uxa8OiMguJJeuQ) -[![spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/emotion) Emotion is a performant and flexible CSS-in-JS library. Building on many other CSS-in-JS libraries, it allows you to style apps quickly with string or object styles. It has predictable composition to avoid specificity issues with CSS. With source maps and labels, Emotion has a great developer experience and great performance with heavy caching in production. diff --git a/docs/babel.mdx b/docs/babel.mdx index 0d748ea02..1d8f2f19c 100644 --- a/docs/babel.mdx +++ b/docs/babel.mdx @@ -2,26 +2,26 @@ title: 'Babel Plugin' --- -`@emotion/babel-plugin` is highly recommended. All of the options that can be provided to `@emotion/babel-plugin` are documented in [`@emotion/babel-plugin`'s README](https://github.com/emotion-js/emotion/tree/main/packages/babel-plugin). +`@emotion/babel-plugin` is highly recommended. All of the options that can be provided to `@emotion/babel-plugin` are documented in [`@emotion/babel-plugin`'s README](/docs/@emotion/babel-plugin). -### Install +## Install In `emotion` version 8 and above, installation is optional. In older versions, installation is required. See the [installation instructions](/docs/install.mdx). -### Features which are enabled with the babel plugin +## Features which are enabled with the babel plugin -### Minification +#### Minification Any leading/trailing space between properties in your `css` and `styled` blocks is removed. This can reduce the size of your final bundle. -### Dead Code Elimination +#### Dead Code Elimination Uglifyjs will use the injected `/*#__PURE__*/` flag comments to mark your `css` and `styled` blocks as candidates for dead code elimination. -### Source Maps +#### Source Maps When enabled, navigate directly to the style declaration in your javascript file. -### Components as selectors +#### Components as selectors The ability to refer to another component to apply override styles depending on nesting context. Learn more in the [styled docs](/docs/styled.mdx#targeting-another-emotion-component). diff --git a/docs/cache-provider.mdx b/docs/cache-provider.mdx index 62e48c3c5..776265ec4 100644 --- a/docs/cache-provider.mdx +++ b/docs/cache-provider.mdx @@ -6,8 +6,7 @@ It can be useful to customize emotion's options - i.e. to add custom Stylis plug ```jsx // @live -/** @jsx jsx */ -import { CacheProvider, jsx, css } from '@emotion/react' +import { CacheProvider, css } from '@emotion/react' import createCache from '@emotion/cache' import { prefixer } from 'stylis' @@ -17,9 +16,10 @@ const myCache = createCache({ key: 'my-prefix-key', stylisPlugins: [ customPlugin, - // has to be included manually when customizing `stylisPlugins` if you want to have vendor prefixes added automatically + // has to be included manually when customizing `stylisPlugins` if you want + // to have vendor prefixes added automatically prefixer - ], + ] }) render( diff --git a/docs/community.mdx b/docs/community.mdx new file mode 100644 index 000000000..c3e0e4074 --- /dev/null +++ b/docs/community.mdx @@ -0,0 +1,52 @@ +--- +title: 'Community' +--- + +> A curated list of awesome stuff related to Emotion. + +## Contents + +- [Libraries](#libraries) +- [Component Libraries](#component-libraries) +- [Examples In the Wild](#examples-in-the-wild) + +## Libraries + +- [facepaint](https://github.com/emotion-js/facepaint) - Responsive style values for css-in-js +- [ember-emotion](https://github.com/alexlafroscia/ember-emotion) - Use emotion in Ember.js +- [vue-emotion](https://github.com/egoist/vue-emotion) - Use emotion in Vue.js +- [CSS to emotion transform](https://transform.now.sh/css-to-emotion/) +- [ShevyJS](https://github.com/kyleshevlin/shevyjs) - Configurable Vertical Rhythm & Typography in CSS-in-JS +- [design-system-utils](https://github.com/mrmartineau/design-system-utils) - Utilities to give better access to your design system. +- [styled-map](https://github.com/scf4/styled-map) - Super simple lib to map props to styles +- [polished](https://github.com/styled-components/polished) - Lightweight set of Sass/Compass-style mixins/helpers for writing styles in JavaScript +- [styled-conditions](https://github.com/karolisgrinkevicius/styled-conditions) - Ultra-lightweight flag utility to conditionally apply css depending on React props +- [manipulative](https://github.com/paulshen/manipulative) - React devtool to style emotion components from the browser +- [emotion-tailwind-preflight](https://github.com/flogy/emotion-tailwind-preflight) - Merge the shiny TailwindCSS base styles into your CSS-in-JS project + +## Component Libraries + +- [react-select](https://github.com/JedWatson/react-select) ([Website](http://jedwatson.github.io/react-select/)) +- [reactivesearch](https://github.com/appbaseio/reactivesearch) ([Website](https://opensource.appbase.io/reactivesearch/)) +- [circuit-ui](https://github.com/sumup/circuit-ui) ([Storybook](https://sumup.github.io/circuit-ui/)) +- [govuk-react](https://github.com/govuk-react/govuk-react) ([Storybook](https://govuk-react.github.io/govuk-react/)) +- [smooth-ui](https://github.com/smooth-code/smooth-ui) ([Website](https://smooth-ui.smooth-code.com/)) +- [material-ui](https://github.com/mui-org/material-ui) ([Website](https://mui.com/)) +- [mineral-ui](https://mineral-ui.com) ([Website](https://mineral-ui.com)) +- [sancho-ui](https://sancho-ui.com) ([Website](https://sancho-ui.com)) + +## Examples In the Wild + +- [healthline.com](https://www.healthline.com/health/body-aches) +- [nytimes.com](https://www.nytimes.com) +- [vault.crucible.gg](http://vault.crucible.gg/) +- [saldotuc.com](https://saldotuc.com) +- [gatsbythemes.com](https://gatsbythemes.com/) +- [blazity.com](https://blazity.com/) +- [postmates.com](https://postmates.com/) +- [thedisconnect.co](https://thedisconnect.co/one) +- [zefenify.com](https://zefenify.com/about.html) +- [strelkamag.com](https://strelkamag.com/) +- [rookout.com](https://www.rookout.com) +- [figma.com](https://www.figma.com/) +- [designsystems.com](https://www.designsystems.com/) diff --git a/docs/composition.mdx b/docs/composition.mdx index d8f478ef0..8157225cf 100644 --- a/docs/composition.mdx +++ b/docs/composition.mdx @@ -6,8 +6,7 @@ Composition is one of the most powerful and useful patterns in Emotion. You can ```jsx // @live -/** @jsx jsx */ -import { jsx, css } from '@emotion/react' +import { css } from '@emotion/react' const base = css` color: hotpink; @@ -54,8 +53,7 @@ With Emotion though, you can create styles and combine them. ```jsx // @live -/** @jsx jsx */ -import { css, jsx } from '@emotion/react' +import { css } from '@emotion/react' const danger = css` color: red; diff --git a/docs/css-prop.mdx b/docs/css-prop.mdx index 01ed45e6a..496dbde70 100644 --- a/docs/css-prop.mdx +++ b/docs/css-prop.mdx @@ -14,10 +14,35 @@ There are 2 ways to get started with the `css` prop. Both methods result in the same compiled code. After adding the preset or setting the pragma as a comment, compiled jsx code will use emotion's `jsx` function instead of `React.createElement`. -| | Input | Output | -| ------ | -------------------------- | --------------------------------------------------- | -| Before | `` | `React.createElement('img', { src: 'avatar.png' })` | -| After | `` | `jsx('img', { src: 'avatar.png' })` | + + + + + + + + + + + + + + + + + + + + +
InputOutput
Before + <img src="avatar.png" /> + + React.createElement('img', { src: 'avatar.png' }) +
After + <img src="avatar.png" /> + + jsx('img', { src: 'avatar.png' }) +
#### Babel Preset @@ -105,9 +130,6 @@ The `css` prop accepts object styles directly and does not require an additional ```jsx // @live -/** @jsx jsx */ -import { jsx } from '@emotion/react' - render(
(

( The `ArticleText` component can be customized and the styles composed with its default styles. The result is passed `P` and the process repeats. -```js -/** @jsx jsx */ -import { jsx } from '@emotion/react' - +```jsx const P = props => (

`@media (min-width: ${bp}px)` -) +const mq = breakpoints.map(bp => `@media (min-width: ${bp}px)`) render(

@@ -86,15 +82,11 @@ npm install --save facepaint ```jsx // @live -/** @jsx jsx */ -import { jsx } from '@emotion/react' import facepaint from 'facepaint' const breakpoints = [576, 768, 992, 1200] -const mq = facepaint( - breakpoints.map(bp => `@media (min-width: ${bp}px)`) -) +const mq = facepaint(breakpoints.map(bp => `@media (min-width: ${bp}px)`)) render(
> Emotion 10 requires `React` 16.3 or greater. -# Thinking +## Thinking The most significant change in Emotion 10 is that it doesn't let you easily access the underlying class names. Instead of thinking in class names, you have to think in terms of styles and composing them together (you can still use class names directly if you want to, but you won't get new features like zero-config server rendering). @@ -31,9 +31,7 @@ let important = css` ` const SomeComponent = props => ( -
+
) ``` @@ -55,13 +53,13 @@ const SomeComponent = props => ( ) ``` -# Incremental Migration +## Incremental Migration Incremental migration is something really important to Emotion because we don't want anyone to have to rewrite their entire app. The upgrades to Emotion 10 are split into two parts. The first part can be done automatically by using [`eslint-plugin-emotion`](./eslint-plugin-emotion#emotion-10-codemods). -## Codemoddable +### Codemoddable - Change all `react-emotion` imports so that `styled` is imported from `@emotion/styled` and all the `emotion` exports are split into a second import. @@ -125,7 +123,7 @@ let element = ( ) ``` -## Manual Steps +### Manual Steps - Add compat cache with provider @@ -145,7 +143,7 @@ render( ) ``` -## Manual Steps Over Time +### Manual Steps Over Time - Change `css` usage to `css` prop @@ -206,7 +204,6 @@ let element = ( - Replace `injectGlobal` with `Global` styles - ```jsx import { injectGlobal } from 'emotion' @@ -220,7 +217,7 @@ injectGlobal` import { Global, css } from '@emotion/core' - - Some text. - A link with a bottom border. + Some text. A link with a bottom border.

) ``` @@ -29,8 +27,7 @@ You can use `&` to select the current class nested in another element: ```jsx // @live -/** @jsx jsx */ -import { jsx, css } from '@emotion/react' +import { css } from '@emotion/react' const paragraph = css` color: turquoise; @@ -42,13 +39,9 @@ const paragraph = css` render(
-

- This is green since it's inside a header -

+

This is green since it's inside a header

-

- This is turquoise since it's not inside a header. -

+

This is turquoise since it's not inside a header.

) ``` diff --git a/docs/object-styles.mdx b/docs/object-styles.mdx index 497be1152..024edca1d 100644 --- a/docs/object-styles.mdx +++ b/docs/object-styles.mdx @@ -10,9 +10,6 @@ Writing styles with objects is a powerful pattern built directly into the core o ```jsx // @live -/** @jsx jsx */ -import { jsx } from '@emotion/react' - render(
- This is a darkorchid button. - -) +render() ``` ### Child Selectors ```jsx // @live -/* @jsx jsx */ -import { jsx } from '@emotion/react' - render(
- This is orange on a big screen and darkorchid on a small - screen. + This is orange on a big screen and darkorchid on a small screen.
) ``` @@ -97,9 +83,6 @@ When numbers are the value of a css property, `px` is appended to the number unl ```jsx // @live -/** @jsx jsx */ -import { jsx } from '@emotion/react' - render(
- This is darkorchid with a hotpink background and 8px of - padding. + This is darkorchid with a hotpink background and 8px of padding.
) ``` @@ -141,22 +120,15 @@ Define fallback values for browsers that don't support features with arrays. ```jsx // @live -/** @jsx jsx */ -import { jsx } from '@emotion/react' - render(
- This has a gradient background in browsers that support - gradients and is red in browsers that don't support - gradients + This has a gradient background in browsers that support gradients and is red + in browsers that don't support gradients
) ``` @@ -167,8 +139,7 @@ You can also use `css` with object styles. ```jsx // @live -/** @jsx jsx */ -import { jsx, css } from '@emotion/react' +import { css } from '@emotion/react' const hotpink = css({ color: 'hotpink' @@ -187,8 +158,7 @@ render( ```jsx // @live -/** @jsx jsx */ -import { jsx, css } from '@emotion/react' +import { css } from '@emotion/react' const hotpink = css({ color: 'hotpink' @@ -209,13 +179,10 @@ const hotpinkWithBlackBackground = css( render(

This is hotpink

- +

- This has a black background and is hotpink. Try moving - where hotpink is in the css call and see if the color - changes. + This has a black background and is hotpink. Try moving where hotpink is in + the css call and see if the color changes.

) diff --git a/docs/source-maps.mdx b/docs/source-maps.mdx index 5d1bee824..dcf2ef067 100644 --- a/docs/source-maps.mdx +++ b/docs/source-maps.mdx @@ -8,7 +8,7 @@ title: 'Source Maps' Emotion supports source maps for styles authored in JavaScript. -![source-map-demo](https://user-images.githubusercontent.com/662750/30778580-78fbeae4-a096-11e7-82e1-120b6984e875.gif) +![Source map demo](https://user-images.githubusercontent.com/662750/30778580-78fbeae4-a096-11e7-82e1-120b6984e875.gif) Required For Source Maps: diff --git a/docs/styled.mdx b/docs/styled.mdx index 4b05a93c4..cd5b1d478 100644 --- a/docs/styled.mdx +++ b/docs/styled.mdx @@ -2,7 +2,7 @@ title: 'Styled Components' --- -`styled` is a way to create React components that have styles attached to them. It's available from [@emotion/styled](/packages/styled). `styled` was heavily inspired by [styled-components](https://www.styled-components.com/) and [glamorous](https://glamorous.rocks/) +`styled` is a way to create React components that have styles attached to them. It's available from [@emotion/styled](/packages/styled). `styled` was heavily inspired by [styled-components](https://www.styled-components.com/) and [glamorous](https://glamorous.rocks/). ### Styling elements and components @@ -28,8 +28,7 @@ Any interpolations or arguments that are functions in `styled` are called with ` import styled from '@emotion/styled' const Button = styled.button` - color: ${props => - props.primary ? 'hotpink' : 'turquoise'}; + color: ${props => (props.primary ? 'hotpink' : 'turquoise')}; ` const Container = styled.div(props => ({ @@ -52,9 +51,8 @@ render( ```jsx // @live import styled from '@emotion/styled' -const Basic = ({ className }) => ( -
Some text
-) + +const Basic = ({ className }) =>
Some text
const Fancy = styled(Basic)` color: hotpink; @@ -75,8 +73,10 @@ const Section = styled.section` background: #333; color: #fff; ` + // this component has the same styles as Section but it renders an aside const Aside = Section.withComponent('aside') + render(
This is a section
@@ -167,8 +167,7 @@ import isPropValid from '@emotion/is-prop-valid' import styled from '@emotion/styled' const H1 = styled('h1', { - shouldForwardProp: prop => - isPropValid(prop) && prop !== 'color' + shouldForwardProp: prop => isPropValid(prop) && prop !== 'color' })(props => ({ color: props.color })) @@ -193,11 +192,7 @@ const dynamicStyle = props => const Container = styled.div` ${dynamicStyle}; ` -render( - - This is lightgreen. - -) +render(This is lightgreen.) ``` ### `as` prop @@ -213,10 +208,7 @@ const Button = styled.button` ` render( - ) @@ -237,14 +229,15 @@ import styled from '@emotion/styled' const Example = styled('span')` color: lightgreen; - & > a { + + & > strong { color: hotpink; } ` render( - This is nested. + This is nested. ) ``` diff --git a/docs/testing.mdx b/docs/testing.mdx index 94d27121c..c55211b5a 100644 --- a/docs/testing.mdx +++ b/docs/testing.mdx @@ -8,7 +8,7 @@ By diffing the serialized value of your React tree Jest can show you what change By default snapshots with emotion show generated class names. Adding [@emotion/jest](https://github.com/emotion-js/emotion/tree/main/packages/jest) allows you to output the actual styles being applied. - + ### Installation @@ -42,9 +42,9 @@ When using Enzyme, you can add `"@emotion/jest/enzyme-serializer"` instead. Or use `expect.addSnapshotSerializer` to add it like this: - ```javascript -import { createEnzymeSerializer } from '@emotion/jest/enzyme-serializer' // also adds the enzyme-to-json serializer +// also adds the enzyme-to-json serializer +import { createEnzymeSerializer } from '@emotion/jest/enzyme-serializer' expect.addSnapshotSerializer(createEnzymeSerializer()) ``` @@ -55,11 +55,8 @@ Writing a test with `@emotion/jest` involves creating a snapshot from the `react ```jsx import React from 'react' -/** @jsx jsx */ -import { jsx } from '@emotion/react' import renderer from 'react-test-renderer' - const Button = props => ( ) - .toJSON() + renderer.create().toJSON() ).toMatchSnapshot() }) ``` diff --git a/docs/theming.mdx b/docs/theming.mdx index 174e853c0..8d1bc5eeb 100644 --- a/docs/theming.mdx +++ b/docs/theming.mdx @@ -21,8 +21,7 @@ Add `ThemeProvider` to the top level of your app and access the theme with `prop ```jsx // @live -/** @jsx jsx */ -import { jsx, ThemeProvider } from '@emotion/react' +import { ThemeProvider } from '@emotion/react' const theme = { colors: { @@ -32,9 +31,7 @@ const theme = { render( -
({ color: theme.colors.primary })}> - some other text -
+
({ color: theme.colors.primary })}>some other text
) ``` @@ -43,8 +40,7 @@ render( ```jsx // @live -/** @jsx jsx */ -import { jsx, ThemeProvider } from '@emotion/react' +import { ThemeProvider } from '@emotion/react' import styled from '@emotion/styled' const theme = { @@ -68,8 +64,7 @@ render( ```jsx // @live -/** @jsx jsx */ -import { jsx, ThemeProvider, useTheme } from '@emotion/react' +import { ThemeProvider, useTheme } from '@emotion/react' const theme = { colors: { @@ -77,14 +72,9 @@ const theme = { } } -function SomeText (props) { +function SomeText(props) { const theme = useTheme() - return ( -
- ) + return
} render( @@ -169,8 +159,7 @@ A React hook that provides the current theme as its value. If the theme is updat ```jsx // @live -/** @jsx jsx */ -import { jsx, ThemeProvider, useTheme } from '@emotion/react' +import { ThemeProvider, useTheme } from '@emotion/react' import styled from '@emotion/styled' const theme = { diff --git a/docs/with-props.mdx b/docs/with-props.mdx index cb760e2ff..4ccdbcdd9 100644 --- a/docs/with-props.mdx +++ b/docs/with-props.mdx @@ -4,16 +4,15 @@ title: 'Attaching Props' Some css-in-js libraries offer APIs to attach props to components, instead of having our own API to do that, we recommend creating a regular react component, using the css prop and attaching props like you would with any other React component. -Note that if css is passed down via props, it will take precedence over the css in the component. +Note that if css is passed down via props, it will take precedence over the css in the component. ```jsx // @live -/** @jsx jsx */ -import { jsx, css } from '@emotion/react' +import { css } from '@emotion/react' const pinkInput = css` background-color: pink; -`; +` const RedPasswordInput = props => (
-); - +) ``` diff --git a/netlify.toml b/netlify.toml index da4124024..3a81094f6 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,9 +1,22 @@ [build] - command = "npm run build && npm run build:site" - publish = "site/public" + command = "yarn build && cd site && yarn build" + publish = "site/out" [build.environment] - NODE_VERSION = "10" - YARN_VERSION = "1.7.0" + NODE_VERSION = "16" + YARN_VERSION = "1.22.19" YARN_FLAGS = "--frozen-lockfile" NETLIFY = "true" + +# These take effect in production and should match the redirects in next.config.js +[[redirects]] + from = "/" + to = "/docs/introduction" + +[[redirects]] + from = "/docs" + to = "/docs/introduction" + +[[redirects]] + from = "/community" + to = "/docs/community" \ No newline at end of file diff --git a/package.json b/package.json index 30ba195a0..57dd7ea7a 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,6 @@ "size": "bundlesize", "lint": "eslint . --fix", "benchmark": "cd scripts/benchmarks && yarn build && yarn run-benchmark", - "start:site": "cd site && yarn start:site", - "build:site": "cd site && yarn build:site", "flow": "flow", "flow:check": "flow check --flowconfig-name=.flowconfig-ci", "preinstall": "node ./scripts/ensure-yarn.js", @@ -184,13 +182,11 @@ "@babel/runtime": "^7.13.10", "@changesets/changelog-github": "^0.4.0", "@changesets/cli": "^2.16.0", - "@manypkg/cli": "^0.16.1", - "@mdx-js/mdx": "^1.6.22", - "@mdx-js/react": "^1.6.22", + "@manypkg/cli": "^0.19.1", "@preconstruct/cli": "^2.1.5", "@testing-library/react": "13.0.0-alpha.5", "@types/jest": "^27.0.3", - "@types/node": "^10.11.4", + "@types/node": "^12.20.37", "@types/react": "^18.0.9", "babel-check-duplicated-nodes": "^1.0.0", "babel-eslint": "^10.1.0", @@ -242,14 +238,9 @@ "prettier": "^2.3.0", "raf": "^3.4.0", "react": "16.14.0", - "react-art": "^16.5.2", "react-dom": "16.14.0", - "react-helmet": "^5.2.0", - "react-icons": "^2.2.7", - "react-live": "1.10.0", "react-native": "^0.63.2", "react-primitives": "^0.8.1", - "react-router-dom": "^4.2.2", "react-test-renderer": "16.8.6", "react18": "npm:react@18.0.0-rc.0-next-aa8f2bdbc-20211215", "react18-dom": "npm:react-dom@18.0.0-rc.0-next-aa8f2bdbc-20211215", @@ -257,8 +248,6 @@ "svg-tag-names": "^1.1.1", "through": "^2.3.8", "unified": "^6.1.6", - "unist-util-visit": "^1.2.0", - "webpack-bundle-analyzer": "3.3.2", - "worker-loader": "2.0.0" + "webpack-bundle-analyzer": "3.3.2" } } diff --git a/packages/babel-preset-css-prop/README.md b/packages/babel-preset-css-prop/README.md index 7040d6187..522874abd 100644 --- a/packages/babel-preset-css-prop/README.md +++ b/packages/babel-preset-css-prop/README.md @@ -1,6 +1,6 @@ # @emotion/babel-preset-css-prop -> A Babel preset to automatically enable Emotion's css prop when using the classic JSX runtime. If you want to use [the new JSX runtimes](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) please do not use this preset but rather just include our [`@emotion/babel-plugin`](/packages/babel-plugin) directly and follow instructions for configuring the new JSX runtimes [here](/docs/css-prop.mdx##babel-preset). +> A Babel preset to automatically enable Emotion's css prop when using the classic JSX runtime. If you want to use [the new JSX runtimes](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) please do not use this preset but rather just include our [`@emotion/babel-plugin`](/packages/babel-plugin) directly and follow instructions for configuring the new JSX runtimes [here](/docs/css-prop.mdx#babel-preset). - [@emotion/babel-preset-css-prop](#emotionbabel-preset-css-prop) - [Install](#install) @@ -77,10 +77,27 @@ require('@babel/core').transform(code, { This preset enables the `css` prop for an entire project via a single entry to the babel configuration. After adding the preset, compiled JSX code will use Emotion's `jsx` function instead of `React.createElement`. -| | Input | Output | -| ------ | -------------------------- | --------------------------------------------------- | -| Before | `` | `React.createElement('img', { src: 'avatar.png' })` | -| After | `` | `jsx('img', { src: 'avatar.png' })` | + + + + + + + + + + + + + + + + + + + + +
InputOutput
Before<img src="avatar.png" />React.createElement('img', { src: 'avatar.png' })
After<img src="avatar.png" />jsx('img', { src: 'avatar.png' })
`import { jsx } from '@emotion/react'` is automatically added to the top of files where required. diff --git a/packages/jest/README.md b/packages/jest/README.md index 16bd3ad06..7f90e5d22 100644 --- a/packages/jest/README.md +++ b/packages/jest/README.md @@ -2,13 +2,13 @@ > Jest testing utilities for emotion -# Installation +## Installation ```bash npm install --save-dev @emotion/jest ``` -# Snapshot Serializer +## Snapshot Serializer The easiest way to test React components with emotion is with the snapshot serializer. You can register the serializer via the `snapshotSerializers` configuration property in your jest configuration like so: @@ -55,9 +55,9 @@ test('renders with correct styles', () => { Refer to the [testing doc][emotion-testing] for more information about snapshot testing with emotion. -## Options +### Options -### `classNameReplacer` +#### `classNameReplacer` @emotion/jest's snapshot serializer replaces the hashes in class names with an index so that things like whitespace changes won't break snapshots. It optionally accepts a custom class name replacer, it defaults to the below. @@ -79,7 +79,7 @@ expect.addSnapshotSerializer( ) ``` -### `DOMElements` +#### `DOMElements` @emotion/jest's snapshot serializer inserts styles and replaces class names in both React and DOM elements. If you would like to disable this behavior for DOM elements, you can do so by passing `{ DOMElements: false }`. For example: @@ -90,7 +90,7 @@ import { createSerializer } from '@emotion/jest' expect.addSnapshotSerializer(createSerializer({ DOMElements: false })) ``` -### `includeStyles` +#### `includeStyles` @emotion/jest's snapshot serializer inserts styles. If you would like to disable this behavior, you can do so by passing `{ includeStyles: false }`. For example: @@ -101,9 +101,9 @@ import { createSerializer } from '@emotion/jest' expect.addSnapshotSerializer(createSerializer({ includeStyles: false })) ``` -# Custom matchers +## Custom matchers -## toHaveStyleRule +### toHaveStyleRule To make more explicit assertions when testing your styled components you can use the `toHaveStyleRule` matcher. diff --git a/packages/native/README.md b/packages/native/README.md index b41a72abd..7301682ed 100644 --- a/packages/native/README.md +++ b/packages/native/README.md @@ -128,54 +128,3 @@ class App extends React.Component { ## Gotchas - Note that the `flex` property works like CSS shorthand, and not the legacy `flex` property in React Native. Setting `flex: 1` sets `flexShrink` to `1` in addition to setting `flexGrow` to `1` and `flexBasis` to `0`. - -## Usage with `react-360` - -`@emotion/native` can also be used with `react-360` for styling VR applications. Check out [this](https://facebook.github.io/react-360/docs/setup.html) guide for setting up a `react-360` project. - -### Example - -```js -import React from 'react' -import { AppRegistry, StyleSheet, Text, View } from 'react-360' -import styled from '@emotion/native' - -const StyledName = styled.Text` - font-size: 40px; - color: hotpink; -` - -export default class App extends React.Component { - render() { - return ( - - - Emotion Native - - - ) - } -} - -const styles = StyleSheet.create({ - panel: { - // Fill the entire surface - width: 1000, - height: 600, - backgroundColor: 'rgba(255, 255, 255, 0.4)', - justifyContent: 'center', - alignItems: 'center' - }, - greetingBox: { - padding: 20, - backgroundColor: '#000000', - borderColor: '#639dda', - borderWidth: 2 - }, - greeting: { - fontSize: 30 - } -}) - -AppRegistry.registerComponent('App', () => App) -``` diff --git a/packages/react/README.md b/packages/react/README.md index 86fc82dbe..d14328453 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -45,4 +45,4 @@ render( ) ``` -More documentation is available at https://emotion.sh. +More documentation is available at [https://emotion.sh](https://emotion.sh). diff --git a/packages/server/package.json b/packages/server/package.json index 768bc5e4a..2e2273559 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -32,7 +32,7 @@ "@emotion/babel-plugin": "11.9.2", "@emotion/css": "11.9.0", "@emotion/css-prettifier": "1.0.1", - "@types/node": "^10.11.4", + "@types/node": "^12.20.37", "typescript": "^4.5.5" }, "author": "Kye Hohenberger", diff --git a/packages/styled/README.md b/packages/styled/README.md index 75a51f7dc..9861dcde4 100644 --- a/packages/styled/README.md +++ b/packages/styled/README.md @@ -28,4 +28,4 @@ render( ) ``` -More documentation is available at https://emotion.sh/docs/styled. +More documentation is available at [https://emotion.sh/docs/styled](https://emotion.sh/docs/styled). diff --git a/playgrounds/nextjs/package.json b/playgrounds/nextjs/package.json index 6c145b987..3d4c99194 100644 --- a/playgrounds/nextjs/package.json +++ b/playgrounds/nextjs/package.json @@ -9,7 +9,7 @@ "start": "next start" }, "dependencies": { - "next": "12.0.2", + "next": "^12.2.0", "react": "16.14.0", "react-dom": "16.14.0" } diff --git a/site/.babelrc.js b/site/.babelrc.js new file mode 100644 index 000000000..43b91527b --- /dev/null +++ b/site/.babelrc.js @@ -0,0 +1,14 @@ +module.exports = { + presets: [ + [ + 'next/babel', + { + 'preset-react': { + runtime: 'automatic', + importSource: '@emotion/react' + } + } + ] + ], + plugins: ['@emotion/babel-plugin'] +} diff --git a/site/CHANGELOG.md b/site/CHANGELOG.md deleted file mode 100644 index 6d8b8a9fd..000000000 --- a/site/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# emotion-site - -## 9.3.0 - -### Minor Changes - -- [8c3fb9d9](https://github.com/emotion-js/emotion/commit/8c3fb9d9) [#1401](https://github.com/emotion-js/emotion/pull/1401) Thanks [@jordanoverbye](https://github.com/jordanoverbye)! - 1. Update document title when the user changes route 2. Update markup to me more semantic (nav, aside, main etc instead of just divs) 3. Fix overflow grid issue 4. Fix broken link on CacheProvider page 5. Remove h1 from pageheader, so there is only 1 h1 on all pages 6. Add more vertical spacing between nav groups in sidebar diff --git a/site/README.md b/site/README.md index 0a97625b6..f2f61f43a 100644 --- a/site/README.md +++ b/site/README.md @@ -1,14 +1,23 @@ # [Emotion Site](https://emotion.sh) +Run the site for development: `yarn dev` (in the `site` directory) + +Preview the production build of the site: `yarn build && yarn start` + ## Docs All of the docs live in the `docs` in the root of this repository. They are in [Markdown](https://daringfireball.net/projects/markdown/basics) and they should include the following frontmatter at the top of each file that specifies the title for the page. ```yaml +--- title: 'Some Title' --- ``` +## Package READMEs + +Links in package READMEs should go to `https://emotion.sh/docs/{docName}` so that clicking the link on npmjs.com brings you to the Emotion website. + ### Code Blocks When the langauge is `jsx` and the code block has a `// @live` comment, it will have a preview next to it. Most of the Emotion packages can be imported and there is a `render` function that accepts a react element and will render into the preview next to it. @@ -20,7 +29,3 @@ When the langauge is `jsx` and the code block has a `// @live` comment, it will render('some react element') \`\`\` ``` - -### Links - -All links to other pages on the docs should be linked to with `https://emotion.sh/docs/doc-name`, these links are transformed so they are local links. diff --git a/site/components/carbon-ads.tsx b/site/components/carbon-ads.tsx new file mode 100644 index 000000000..618192e24 --- /dev/null +++ b/site/components/carbon-ads.tsx @@ -0,0 +1,114 @@ +import { ReactElement, useEffect, useRef } from 'react' +import { Global, css } from '@emotion/react' +import { useRouter } from 'next/router' +import { mediaQueries } from '../util' + +const carbonCss = css` + #carbonads { + display: block; + overflow: hidden; + } + + #carbonads a, + #carbonads a:hover { + color: inherit; + text-decoration: none; + } + + #carbonads span { + display: block; + + overflow: hidden; + } + + #carbonads .carbon-img { + display: block; + + line-height: 1; + } + + #carbonads .carbon-img img { + display: block; + border: solid 1px hsla(0, 0%, 7%, 0.1); + } + + #carbonads .carbon-text { + display: block; + margin-top: 0.5em; + + font-size: 14px; + line-height: 1.35; + } + + #carbonads .carbon-poweredby { + display: block; + margin-top: 0.5em; + + font-size: 10px; + font-weight: 600; + line-height: 1; + letter-spacing: 0.1ch; + text-transform: uppercase; + } + + @media only screen and (min-width: 320px) and (max-width: 759px) { + #carbonads { + position: relative; + } + + #carbonads .carbon-wrap { + display: flex; + flex-direction: row; + } + + #carbonads .carbon-text { + padding: 0 12px; + font-size: 16px; + } + + #carbonads .carbon-poweredby { + padding-left: 142px; + } + } +` + +export function CarbonAds(): ReactElement { + const ref = useRef(null) + + useEffect(() => { + if (!ref.current) return + + const script = document.createElement('script') + script.async = true + script.id = '_carbonads_js' // This ID is required for the Carbon JS to work + script.src = + 'https://cdn.carbonads.com/carbon.js?serve=CESDV5QY&placement=emotionsh' + + ref.current.appendChild(script) + }, []) + + const router = useRouter() + + // Change the ad when the user navigates to a different doc + useEffect(() => { + const _global = globalThis as { _carbonads?: { refresh(): void } } + + _global._carbonads?.refresh() + }, [router.asPath]) + + return ( + <> + +
+ + ) +} diff --git a/site/components/container.tsx b/site/components/container.tsx new file mode 100644 index 000000000..b6c35e537 --- /dev/null +++ b/site/components/container.tsx @@ -0,0 +1,30 @@ +import { PropsWithChildren, ReactElement } from 'react' +import { mediaQueries } from '../util' + +type ContainerProps = PropsWithChildren<{ + className?: string +}> + +export function Container({ + className, + children +}: PropsWithChildren): ReactElement { + return ( +
+ {children} +
+ ) +} diff --git a/site/components/doc-wrapper.tsx b/site/components/doc-wrapper.tsx new file mode 100644 index 000000000..8cb82dbd2 --- /dev/null +++ b/site/components/doc-wrapper.tsx @@ -0,0 +1,208 @@ +import { faBars, faTimes } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { PropsWithChildren, ReactElement, useEffect, useState } from 'react' +import { DocGroup, DocMetadata } from '../queries' +import { colors, mediaQueries } from '../util' +import { CarbonAds } from './carbon-ads' +import { markdownCss } from './markdown-css' +import { Search } from './search' + +interface ToggleSidebarButtonProps { + sidebarOpen: boolean + onClick(): void +} + +function ToggleSidebarButton({ + sidebarOpen, + onClick +}: ToggleSidebarButtonProps) { + return ( + + ) +} + +interface SidebarGroupProps { + activeSlug: string + title: string + docs: DocMetadata[] +} + +function SidebarGroup({ + activeSlug, + title, + docs +}: SidebarGroupProps): ReactElement { + return ( + <> +

+ {title} +

+
    + {docs + .filter(doc => doc.slug !== 'community') + .map(doc => ( +
  • + + + {doc.title} + + +
  • + ))} +
+ + ) +} + +interface DocWrapperProps { + activeSlug: string + docGroups: DocGroup[] +} + +export function DocWrapper({ + activeSlug, + docGroups, + children +}: PropsWithChildren): ReactElement { + const [sidebarOpen, setSidebarOpen] = useState(false) + + const path = useRouter().asPath + + // Close sidebar when path changes + useEffect(() => { + setSidebarOpen(false) + }, [path]) + + return ( + <> +
+
+ {children} +
+ +
+ setSidebarOpen(!sidebarOpen)} + /> + + ) +} diff --git a/site/components/global-styles.tsx b/site/components/global-styles.tsx new file mode 100644 index 000000000..8da8e4d3f --- /dev/null +++ b/site/components/global-styles.tsx @@ -0,0 +1,18 @@ +import { Global, css } from '@emotion/react' +import { ReactElement } from 'react' +import { colors, draculaPrism } from '../util' + +const globalCss = css({ + a: { + color: colors.pink, + textDecoration: 'none' + }, + 'a:hover': { + color: colors.hightlight, + textDecoration: 'underline' + } +}) + +export function GlobalStyles(): ReactElement { + return +} diff --git a/site/components/index.ts b/site/components/index.ts new file mode 100644 index 000000000..81f6c2d2b --- /dev/null +++ b/site/components/index.ts @@ -0,0 +1,6 @@ +export * from './site-header' +export * from './doc-wrapper' +export * from './title' +export * from './global-styles' +export * from './markdown-css' +export * from './container' diff --git a/site/components/live-editor/compiler/babel-worker.ts b/site/components/live-editor/compiler/babel-worker.ts new file mode 100644 index 000000000..38f8fc83e --- /dev/null +++ b/site/components/live-editor/compiler/babel-worker.ts @@ -0,0 +1,60 @@ +import * as Babel from '@babel/standalone' + +import emotionBabelPlugin from '@emotion/babel-plugin' +import { + CompilationFailureMessage, + CompilationRequestMessage, + CompilationSuccessMessage +} from './message' + +const options = { + presets: [ + // Convert imports to require + [Babel.availablePresets['env'], { modules: 'commonjs' }], + [ + Babel.availablePresets['react'], + { runtime: 'automatic', importSource: '@emotion/react' } + ] + ], + plugins: [[emotionBabelPlugin, { sourceMap: false }]] +} + +function compile(code: string): string { + const result = Babel.transform(code, options).code + if (!result) throw new Error('Babel failed to compile the code.') + + return result +} + +function hasMessageProperty(e: any): e is { message: string } { + return typeof e.message === 'string' +} + +addEventListener( + 'message', + ({ data }: MessageEvent) => { + const { id, code } = data + + try { + const compiledCode = compile(code) + + const response: CompilationSuccessMessage = { + id, + type: 'success', + compiledCode + } + postMessage(response) + } catch (e) { + const errorMessage: string = hasMessageProperty(e) + ? e.message + : 'There was an unknown error while compiling your code.' + + const response: CompilationFailureMessage = { + id, + type: 'failure', + errorMessage + } + postMessage(response) + } + } +) diff --git a/site/components/live-editor/compiler/compile.ts b/site/components/live-editor/compiler/compile.ts new file mode 100644 index 000000000..604a86ddf --- /dev/null +++ b/site/components/live-editor/compiler/compile.ts @@ -0,0 +1,51 @@ +import { + CompilationFailureMessage, + CompilationRequestMessage, + CompilationSuccessMessage +} from './message' + +// Instantiate the worker in the browser only +const worker = + typeof Worker === 'function' + ? new Worker(new URL('babel-worker.ts', import.meta.url)) + : undefined + +let count = 0 + +export function compile(code: string): Promise { + return new Promise((resolve, reject) => { + if (!worker) { + resolve('') + return + } + + const id = ++count + + function handler({ + data + }: MessageEvent< + CompilationSuccessMessage | CompilationFailureMessage + >): void { + if (data.id !== id) return + worker!.removeEventListener('message', handler) + + switch (data.type) { + case 'success': + resolve(data.compiledCode) + break + case 'failure': + reject(new Error(data.errorMessage)) + break + default: + throw new Error('Received an unexpected message.') + } + } + worker.addEventListener('message', handler) + + const request: CompilationRequestMessage = { + id, + code + } + worker.postMessage(request) + }) +} diff --git a/site/components/live-editor/compiler/index.ts b/site/components/live-editor/compiler/index.ts new file mode 100644 index 000000000..34c01eb12 --- /dev/null +++ b/site/components/live-editor/compiler/index.ts @@ -0,0 +1 @@ +export * from './compile' diff --git a/site/components/live-editor/compiler/message.ts b/site/components/live-editor/compiler/message.ts new file mode 100644 index 000000000..4b3e9547e --- /dev/null +++ b/site/components/live-editor/compiler/message.ts @@ -0,0 +1,16 @@ +export interface CompilationRequestMessage { + id: number + code: string +} + +export interface CompilationSuccessMessage { + id: number + type: 'success' + compiledCode: string +} + +export interface CompilationFailureMessage { + id: number + type: 'failure' + errorMessage: string +} diff --git a/site/components/live-editor/components/README.md b/site/components/live-editor/components/README.md new file mode 100644 index 000000000..ae8a58712 --- /dev/null +++ b/site/components/live-editor/components/README.md @@ -0,0 +1 @@ +These components were forked from [react-live](https://github.com/FormidableLabs/react-live). diff --git a/site/components/live-editor/components/error-boundary.tsx b/site/components/live-editor/components/error-boundary.tsx new file mode 100644 index 000000000..b34902ee5 --- /dev/null +++ b/site/components/live-editor/components/error-boundary.tsx @@ -0,0 +1,15 @@ +import React, { PropsWithChildren, ReactNode } from 'react' + +type ErrorBoundaryProps = PropsWithChildren<{ + onError(error: unknown): void +}> + +export class ErrorBoundary extends React.Component { + componentDidCatch(error: unknown): void { + this.props.onError(error) + } + + render(): ReactNode { + return this.props.children + } +} diff --git a/site/components/live-editor/components/index.ts b/site/components/live-editor/components/index.ts new file mode 100644 index 000000000..93c5f955c --- /dev/null +++ b/site/components/live-editor/components/index.ts @@ -0,0 +1,5 @@ +export * from './live-editor' +export * from './live-provider' +export * from './live-error' +export * from './live-preview' +export type { Scope } from './render-element-async' diff --git a/site/components/live-editor/components/live-context.ts b/site/components/live-editor/components/live-context.ts new file mode 100644 index 000000000..13002e080 --- /dev/null +++ b/site/components/live-editor/components/live-context.ts @@ -0,0 +1,17 @@ +import React, { ReactElement } from 'react' + +export interface LiveContextData { + code: string + onCodeChange(code: string): void + + element: ReactElement | undefined + errorMessage: string | undefined +} + +export const LiveContext = React.createContext({ + code: '', + onCodeChange: () => {}, + + element: undefined, + errorMessage: undefined +}) diff --git a/site/components/live-editor/components/live-editor.tsx b/site/components/live-editor/components/live-editor.tsx new file mode 100644 index 000000000..7a521a40c --- /dev/null +++ b/site/components/live-editor/components/live-editor.tsx @@ -0,0 +1,54 @@ +import Highlight from 'prism-react-renderer' +import { useContext, ReactElement } from 'react' +import { LiveContext } from './live-context' +import SimpleCodeEditor from 'react-simple-code-editor' + +// Use our customized instance of Prism, see prism-highlight-css.ts +import Prism from 'prismjs' +import { styleConstants } from '../../../util' + +interface LiveEditorProps { + className?: string +} + +export function LiveEditor({ className }: LiveEditorProps): ReactElement { + const { code, onCodeChange } = useContext(LiveContext) + + function highlightCode(code: string): ReactElement { + // `Prism as any` is necessary because of this issue: https://github.com/FormidableLabs/prism-react-renderer/issues/136 + return ( + + {({ tokens, getLineProps, getTokenProps }) => ( + <> + {tokens.map((line, i) => ( + // eslint-disable-next-line react/jsx-key +
+ {line.map((token, key) => ( + // eslint-disable-next-line react/jsx-key + + ))} +
+ ))} + + )} +
+ ) + } + + return ( + + ) +} diff --git a/site/components/live-editor/components/live-error.tsx b/site/components/live-editor/components/live-error.tsx new file mode 100644 index 000000000..5b6538ca2 --- /dev/null +++ b/site/components/live-editor/components/live-error.tsx @@ -0,0 +1,14 @@ +import { useContext, ReactElement } from 'react' +import { LiveContext } from './live-context' + +interface LiveErrorProps { + className?: string +} + +export function LiveError({ className }: LiveErrorProps): ReactElement | null { + const { errorMessage } = useContext(LiveContext) + + if (!errorMessage) return null + + return
{errorMessage}
+} diff --git a/site/components/live-editor/components/live-preview.tsx b/site/components/live-editor/components/live-preview.tsx new file mode 100644 index 000000000..7ee2e7210 --- /dev/null +++ b/site/components/live-editor/components/live-preview.tsx @@ -0,0 +1,12 @@ +import { ReactElement, useContext } from 'react' +import { LiveContext } from './live-context' + +interface LivePreviewProps { + className?: string +} + +export function LivePreview({ className }: LivePreviewProps): ReactElement { + const { element } = useContext(LiveContext) + + return
{element}
+} diff --git a/site/components/live-editor/components/live-provider.tsx b/site/components/live-editor/components/live-provider.tsx new file mode 100644 index 000000000..ab436c1d6 --- /dev/null +++ b/site/components/live-editor/components/live-provider.tsx @@ -0,0 +1,60 @@ +import { PropsWithChildren, ReactElement, useEffect, useState } from 'react' +import { LiveContext } from './live-context' +import { renderElementAsync, Scope } from './render-element-async' + +type LiveProviderProps = PropsWithChildren<{ + defaultCode: string + scope: Scope + + transformCode(code: string): Promise +}> + +export function LiveProvider({ + defaultCode, + scope, + transformCode, + children +}: LiveProviderProps) { + const [errorMessage, setErrorMessage] = useState() + const [element, setElement] = useState() + const [code, setCode] = useState(defaultCode) + + useEffect(() => { + async function effectAsync(): Promise { + function onError(e: unknown): void { + setErrorMessage((e as { toString(): string }).toString()) + setElement(undefined) + } + + try { + const transformedCode = await transformCode(code) + + setErrorMessage(undefined) + + const element = await renderElementAsync( + transformedCode, + scope, + onError + ) + setElement(element) + } catch (e) { + onError(e) + } + } + + effectAsync() + }, [code, scope, transformCode]) + + return ( + + {children} + + ) +} diff --git a/site/components/live-editor/components/render-element-async.tsx b/site/components/live-editor/components/render-element-async.tsx new file mode 100644 index 000000000..105b17e31 --- /dev/null +++ b/site/components/live-editor/components/render-element-async.tsx @@ -0,0 +1,32 @@ +import React, { ReactElement } from 'react' +import { ErrorBoundary } from './error-boundary' + +export type Scope = Record + +function evalCode(code: string, scope: Scope) { + const scopeKeys = Object.keys(scope) + const scopeValues = Object.values(scope) + + const func = new Function('React', ...scopeKeys, code) + return func(React, ...scopeValues) +} + +export async function renderElementAsync( + code: string, + scope: Scope, + onError: (error: unknown) => void +): Promise { + return new Promise((resolve, reject) => { + function render(element: ReactElement) { + if (!element) reject(new Error('`render` must be called with valid JSX.')) + + resolve({element}) + } + + if (!/render\s*\(/.test(code)) { + throw new Error('You must call `render`.') + } + + evalCode(code, { ...scope, render }) + }) +} diff --git a/site/components/live-editor/emotion-live-editor.tsx b/site/components/live-editor/emotion-live-editor.tsx new file mode 100644 index 000000000..469d513f2 --- /dev/null +++ b/site/components/live-editor/emotion-live-editor.tsx @@ -0,0 +1,139 @@ +import { ReactElement } from 'react' +import { + LiveProvider, + LiveError, + LivePreview, + LiveEditor, + Scope +} from './components' +import { css } from '@emotion/react' +import { colors, mediaQueries, styleConstants } from '../../util' +import { compile } from './compiler' + +const scope: Scope = { + process: { + env: { + NODE_ENV: process.env.NODE_ENV + } + }, + require(moduleName: string) { + switch (moduleName) { + case '@emotion/css': + return require('@emotion/css') + case '@emotion/cache': + return require('@emotion/cache') + case '@emotion/react': + return require('@emotion/react') + case '@emotion/react/jsx-runtime': + return require('@emotion/react/jsx-runtime') + case '@emotion/styled': + return require('@emotion/styled') + case '@emotion/styled/base': + return require('@emotion/styled/base') + case '@emotion/is-prop-valid': + return require('@emotion/is-prop-valid') + case 'facepaint': + return require('facepaint') + case 'stylis': + return require('stylis') + + // Not used unless the user adds a jsxImportSource directive + case 'react/jsx-runtime': + return require('react/jsx-runtime') + + default: + throw new Error(`Module "${moduleName}" not found.`) + } + } +} + +const borderRadius = '0.5rem' + +const theCss = { + container: css({ + [mediaQueries.mdUp]: { + display: 'flex' + } + }), + + editor: css({ + backgroundColor: 'rgb(40, 41, 54)', // Copied from Prism theme + caretColor: 'white', + + borderTopLeftRadius: borderRadius, + borderTopRightRadius: borderRadius, + + [mediaQueries.mdUp]: { + width: '50%', + borderBottomLeftRadius: borderRadius, + borderTopRightRadius: 0 + } + }), + + result: css({ + padding: '0.5rem', + display: 'flex', + flexDirection: 'column', + + border: `1px solid ${colors.grayBorder}`, + borderTopStyle: 'none', + borderBottomLeftRadius: borderRadius, + borderBottomRightRadius: borderRadius, + + [mediaQueries.mdUp]: { + width: '50%', + borderTopStyle: 'solid', + borderLeftStyle: 'none', + borderTopRightRadius: borderRadius, + borderBottomLeftRadius: 0 + } + }), + + label: css({ + textAlign: 'center', + fontSize: styleConstants.fontSizeSm, + color: colors.gray500 + }), + + error: css({ + flex: '1', + padding: '0.5rem', + marginBottom: 0, + color: colors.danger + }), + + preview: css({ + flex: 1, + padding: '0.5rem 0.5rem 1.5rem 0.5rem', + overflowX: 'auto', + + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + + '&:empty': { + display: 'none' + } + }) +} + +interface LiveEditorProps { + code: string +} + +export function EmotionLiveEditor({ code }: LiveEditorProps): ReactElement { + return ( +
+
+ + +
+ + +
(Edit code to see changes)
+
+
+
+
+ ) +} diff --git a/site/components/live-editor/index.ts b/site/components/live-editor/index.ts new file mode 100644 index 000000000..de74684c4 --- /dev/null +++ b/site/components/live-editor/index.ts @@ -0,0 +1,2 @@ +export * from './remark-live-editor' +export * from './emotion-live-editor' diff --git a/site/components/live-editor/remark-live-editor.ts b/site/components/live-editor/remark-live-editor.ts new file mode 100644 index 000000000..cf722ccc3 --- /dev/null +++ b/site/components/live-editor/remark-live-editor.ts @@ -0,0 +1,34 @@ +import { visit } from 'unist-util-visit' + +const liveRegex = /^\s*\/\/ @live/ + +interface CodeNode { + type: string + lang?: string | null + value?: string | null + + name?: string | null + attributes?: { type: 'mdxJsxAttribute'; name: string; value: string }[] +} + +export function remarkLiveEditor() { + return (markdownAST: any) => { + visit(markdownAST, 'code', (node: CodeNode) => { + if ( + node.lang === 'jsx' && + // This won't work if you don't use line comments but adding a whole + // JS parser would be overkill + node.value && + liveRegex.test(node.value) + ) { + const code = node.value.replace('// @live', '').trim() + + node.type = 'mdxJsxFlowElement' + node.name = 'EmotionLiveEditor' + node.attributes = [ + { type: 'mdxJsxAttribute', name: 'code', value: code } + ] + } + }) + } +} diff --git a/site/components/markdown-css.tsx b/site/components/markdown-css.tsx new file mode 100644 index 000000000..aaa9be41b --- /dev/null +++ b/site/components/markdown-css.tsx @@ -0,0 +1,123 @@ +import { css } from '@emotion/react' +import React, { ReactElement } from 'react' +import { colors, mediaQueries, styleConstants } from '../util' + +export const markdownCss = css({ + 'h2, h3, h4': { marginTop: '2rem', marginBottom: '1rem' }, + + h2: { + paddingBottom: '0.5rem', + borderBottom: `1px solid ${colors.pinkBorder}` + }, + + 'p, li, code': { lineHeight: 1.7 }, + + a: { + fontWeight: 500, + color: colors.hightlight + }, + + '.icon-link': { + display: 'none', + float: 'left', + paddingRight: '0.5rem', + marginLeft: '-1.75rem', + lineHeight: 1, + width: '1.75rem', + height: '1.25rem', + backgroundImage: 'url(/link.svg)', + backgroundSize: '1.25rem', + backgroundRepeat: 'no-repeat' + }, + + 'h2 .icon-link': { + marginTop: '0.75rem' + }, + + 'h3 .icon-link': { + marginTop: '0.5rem' + }, + + 'h4 .icon-link': { + marginTop: '0.4rem' + }, + + 'h2:hover, h3:hover, h4:hover': { + '.icon-link': { + display: 'block' + } + }, + + hr: { border: `1px solid ${colors.pinkBorder}`, opacity: 1 }, + + // This targets inline code only + 'code:not([class*="language-"])': { + color: colors.body, + backgroundColor: 'rgba(117, 63, 131, 0.07)', + borderRadius: styleConstants.borderRadius, + padding: '0.25rem' + }, + + // This targets code blocks only + '.remark-highlight pre code': { + fontSize: '1rem !important' + }, + + // This targets code blocks and live editors + '.remark-highlight, .emotion-live-editor': { + margin: '1.5rem 0' + }, + + blockquote: { + display: 'flex', + margin: '1.5rem 0', + borderLeft: `5px solid ${colors.hotPink}`, + backgroundColor: colors.pinkBg, + padding: '1rem', + + p: { + margin: 0, + + '&:first-of-type': { + fontWeight: 700 + }, + + '&:nth-of-type(2)': { + marginLeft: '0.5rem', + padding: '0 1rem' + } + } + }, + + '.table-responsive-sm': { + overflowX: 'auto', + + [mediaQueries.mdUp]: { + overflowX: 'initial' + } + }, + + table: { + margin: '1.5rem 0', + width: '100%' + }, + + 'td, th': { + padding: '0.75rem', + border: `1px solid ${colors.grayBorder}` + }, + + img: { + maxWidth: '100%' + } +}) + +export function ResponsiveTable({ + children +}: React.TableHTMLAttributes): ReactElement { + return ( +
+ {children}
+
+ ) +} diff --git a/site/components/search.tsx b/site/components/search.tsx new file mode 100644 index 000000000..09db6b033 --- /dev/null +++ b/site/components/search.tsx @@ -0,0 +1,28 @@ +import { DocSearch } from '@docsearch/react' +import { css, Global } from '@emotion/react' +import { ReactElement } from 'react' + +const docSearchCustomizationCss = css({ + '.DocSearch-Button': { + margin: '0 auto 2rem 0', + width: '100%' + }, + + // Display the "Search" placeholder regardless of screen width + '.DocSearch-Button-Placeholder': { + display: 'block !important' + } +}) + +export function Search(): ReactElement { + return ( + <> + + + + ) +} diff --git a/site/components/site-header.tsx b/site/components/site-header.tsx new file mode 100644 index 000000000..30a9a4520 --- /dev/null +++ b/site/components/site-header.tsx @@ -0,0 +1,209 @@ +import { PropsWithChildren, ReactElement } from 'react' +import Link from 'next/link' +import { colors, mediaQueries, styleConstants } from '../util' +import { css } from '@emotion/react' +import { useRouter } from 'next/router' +import { Container } from './container' + +export const animatedUnderline = css({ + '&::after': { + content: '""', + display: 'block', + width: '100%', + marginTop: 2, + height: 3, + transition: 'transform 250ms ease', + transform: 'scaleX(0)', + backgroundColor: colors.hightlight + }, + '&.active::after, &:hover::after': { + transform: 'scaleX(1)' + } +}) + +type HeaderLinkProps = PropsWithChildren<{ href: string; active?: boolean }> + +function HeaderLink({ + href, + active = false, + children +}: HeaderLinkProps): ReactElement { + return ( + + + {children} + + + ) +} + +function UkraineBanner() { + return ( + +
+ + 🇺🇦 STOP WAR IN UKRAINE 🇺🇦 + +
+
+ ) +} + +export function SiteHeader() { + const router = useRouter() + + const path = router.asPath + const onCommunityPage = path === '/docs/community' + + return ( + <> + +
+ + + + {/* next/image is not compatible with `next export` */} + Avatar +

+ Emotion +

+
+ + +
+
+ + ) +} diff --git a/site/components/title.tsx b/site/components/title.tsx new file mode 100644 index 000000000..a06fafe25 --- /dev/null +++ b/site/components/title.tsx @@ -0,0 +1,17 @@ +import { PropsWithChildren, ReactElement } from 'react' + +type TitleProps = PropsWithChildren<{ className?: string }> + +export function Title({ className, children }: TitleProps): ReactElement { + return ( +

+ {children} +

+ ) +} diff --git a/site/docs-yaml.js b/site/docs-yaml.js deleted file mode 100644 index 4fbe7b881..000000000 --- a/site/docs-yaml.js +++ /dev/null @@ -1,10 +0,0 @@ -const fs = require('fs') -const path = require('path') -const yaml = require('js-yaml') -const packageYamlPath = path.resolve(__dirname, '../docs/docs.yaml') - -module.exports = () => { - const yamlString = fs.readFileSync(packageYamlPath).toString() - - return yaml.safeLoad(yamlString) -} diff --git a/site/gatsby-config.js b/site/gatsby-config.js deleted file mode 100644 index 662a52a92..000000000 --- a/site/gatsby-config.js +++ /dev/null @@ -1,92 +0,0 @@ -const path = require('path') -const packages = require('./docs-yaml')().filter( - ({ title }) => title === 'Packages' -)[0].items - -module.exports = { - siteMetadata: { - siteUrl: 'https://emotion.sh', - title: `emotion` - }, - plugins: packages - .map(pkg => - path.resolve( - `${__dirname}/../packages/${pkg.replace('@emotion/', '')}/README.md` - ) - ) - .map(file => ({ - resolve: 'gatsby-source-filesystem', - options: { - path: file - } - })) - .concat([ - { - resolve: 'gatsby-source-filesystem', - options: { - name: 'docs', - path: `${__dirname}/../docs` - } - }, - { - resolve: 'gatsby-source-filesystem', - options: { - path: `${__dirname}/../emotion.png` - } - }, - { - // todo: contribute to gatsby-plugin-manifest - // https://github.com/gatsbyjs/gatsby/issues/5887 - resolve: `gatsby-plugin-favicon-fork`, - options: { - logo: `${__dirname}/../emotion.png`, - injectHTML: true, - icons: { - android: false, - appleIcon: true, - appleStartup: false, - coast: false, - favicons: true, - firefox: false, - twitter: false, - yandex: false, - windows: false - } - } - }, - 'gatsby-plugin-emotion-next-compat', - { - resolve: `gatsby-plugin-mdx`, - options: { - extensions: ['.mdx', '.md'], - gatsbyRemarkPlugins: [ - { - resolve: require.resolve( - './plugins/gatsby-remark-remove-readme-titles' - ) - }, - { resolve: require.resolve('./plugins/gatsby-remark-fix-links') }, - { - resolve: require.resolve('./plugins/gatsby-remark-change-awesome') - }, - { resolve: require.resolve('./plugins/gatsby-remark-live-code') }, - { resolve: 'gatsby-remark-autolink-headers' }, - { resolve: 'gatsby-remark-prismjs' }, - { resolve: 'gatsby-remark-smartypants' } - ] - } - }, - { - resolve: `gatsby-plugin-google-analytics`, - options: { - trackingId: 'UA-101206186-1' - } - }, - `gatsby-plugin-react-helmet`, - 'gatsby-plugin-sharp', - 'gatsby-transformer-sharp', - 'gatsby-plugin-catch-links', - 'gatsby-plugin-sitemap', - 'gatsby-plugin-netlify' - ]) -} diff --git a/site/gatsby-node.js b/site/gatsby-node.js deleted file mode 100644 index 93c64b629..000000000 --- a/site/gatsby-node.js +++ /dev/null @@ -1,139 +0,0 @@ -const path = require('path') -var BundleAnalyzerPlugin = - require('webpack-bundle-analyzer').BundleAnalyzerPlugin -global.Babel = require('@babel/standalone') - -exports.onCreateWebpackConfig = ({ stage, actions, plugins, getConfig }) => { - actions.setWebpackConfig({ - // xor and props are for react-live and cosmiconfig is for babel-plugin-macros - plugins: [plugins.ignore(/^(xor|props|cosmiconfig)$/)], - resolve: { - alias: { - assert: 'fbjs/lib/emptyFunction', - 'source-map': 'fbjs/lib/emptyFunction', - 'convert-source-map': 'fbjs/lib/emptyFunction', - '@babel/types': path.join(__dirname, './src/utils/babel-types') - } - }, - node: { - fs: 'empty', - buffer: 'empty', - assert: 'empty' - } - }) - const config = getConfig() - actions.replaceWebpackConfig({ - ...config, - output: { - ...config.output, - // this doesn't seem to always merge correctly with `setWebpackConfig` for some reason - // so i'm setting it here - // this is here because it defaults to window and is used for hot reloading and other stuff - // so if this wasn't here, the web worker would break - // since it would try to access window - globalObject: 'this' - }, - module: { - ...config.module, - rules: config.module.rules.filter(rule => { - // eslint is annoying - return rule.enforce !== 'pre' - }) - } - }) - - if (stage === 'build-javascript' && !process.env.NETLIFY) { - actions.setWebpackConfig({ - plugins: [ - new BundleAnalyzerPlugin({ - analyzerMode: 'static' - }) - ] - }) - } -} - -const { createRemoteFileNode } = require(`gatsby-source-filesystem`) - -exports.sourceNodes = async ({ store, cache, actions, createNodeId }) => { - await createRemoteFileNode({ - url: `https://raw.githubusercontent.com/emotion-js/awesome-emotion/master/README.md`, - store, - cache, - createNode: actions.createNode, - createNodeId - }) -} - -exports.createPages = async ({ graphql, actions }) => { - const { createPage, createRedirect } = actions - - createRedirect({ - fromPath: `/`, - isPermanent: true, - redirectInBrowser: true, - toPath: `/docs/introduction` - }) - - createRedirect({ - fromPath: `/docs`, - isPermanent: true, - redirectInBrowser: true, - toPath: `/docs/introduction` - }) - - const docs1 = require('./docs-yaml')() - const docTemplate = require.resolve(`./src/templates/doc.js`) - docs1.forEach(({ title, items }) => { - items.forEach(itemName => { - createPage({ - path: `docs/${itemName}`, - component: docTemplate, - context: { - slug: itemName - } - }) - }) - }) -} - -// Add custom url pathname for blog posts. -exports.onCreateNode = async ({ node, actions, getNode, loadNodeContent }) => { - const { createNodeField } = actions - - if (node.internal.type === `Mdx` && typeof node.slug === `undefined`) { - const fileNode = getNode(node.parent) - - createNodeField({ - node, - name: `slug`, - value: - fileNode.name === 'README' - ? getNameForPackage(fileNode.absolutePath) - : fileNode.name - }) - } -} - -function getNameForPackage(absolutePath) { - try { - return require(`${path.parse(absolutePath).dir}/package.json`).name - } catch (e) { - if (e.code === 'MODULE_NOT_FOUND') { - const splitAbsolutePath = absolutePath.split(path.sep) - return splitAbsolutePath[splitAbsolutePath.length - 2] - } - throw e - } -} - -exports.onCreateBabelConfig = ({ actions, stage }) => { - actions.setBabelPreset({ - name: `babel-preset-emotion-dev`, - stage - }) - actions.setBabelPreset({ - name: require.resolve(`@emotion/babel-preset-css-prop`), - stage - }) -} diff --git a/site/misc.d.ts b/site/misc.d.ts new file mode 100644 index 000000000..95ac1692e --- /dev/null +++ b/site/misc.d.ts @@ -0,0 +1 @@ +declare module '@emotion/babel-plugin' // TODO Remove as part of TS migration diff --git a/site/module-stubs/README.md b/site/module-stubs/README.md new file mode 100644 index 000000000..db8708610 --- /dev/null +++ b/site/module-stubs/README.md @@ -0,0 +1,5 @@ +`@emotion/babel-plugin` depends on some modules which require `fs` which obviously cannot be imported in the browser. (Furthermore, it's unclear if Next.js will allow you to require `fs` during SSR, if the require is located in code that is shared between client and server.) + +So to get `@emotion/babel-plugin` to work in the browser / Next.js, we have to stub out any dependencies which require `fs`. This works via Webpack `require.alias` in `next.config.js`. + +This is a very hacky workaround that could easily break things in the future. diff --git a/site/module-stubs/cosmiconfig.cjs b/site/module-stubs/cosmiconfig.cjs new file mode 100644 index 000000000..44368bf6e --- /dev/null +++ b/site/module-stubs/cosmiconfig.cjs @@ -0,0 +1 @@ +module.exports = () => ({}) diff --git a/site/module-stubs/find-root.cjs b/site/module-stubs/find-root.cjs new file mode 100644 index 000000000..9d7c14c12 --- /dev/null +++ b/site/module-stubs/find-root.cjs @@ -0,0 +1,3 @@ +module.exports = function findRoot() { + return '' +} diff --git a/site/module-stubs/resolve.cjs b/site/module-stubs/resolve.cjs new file mode 100644 index 000000000..64678900f --- /dev/null +++ b/site/module-stubs/resolve.cjs @@ -0,0 +1 @@ +module.exports = () => 'FAKE_PATH' diff --git a/site/next-env.d.ts b/site/next-env.d.ts new file mode 100644 index 000000000..4f11a03dc --- /dev/null +++ b/site/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/site/next.config.js b/site/next.config.js new file mode 100644 index 000000000..96946ec0e --- /dev/null +++ b/site/next.config.js @@ -0,0 +1,49 @@ +const path = require('path') + +// To run bundle analyzer, set the `ANALYZE` environment variable to 'true'. +const withBundleAnalyzer = require('@next/bundle-analyzer')({ + enabled: process.env.ANALYZE === 'true' +}) + +/** @type {import('next').NextConfig} */ +module.exports = withBundleAnalyzer({ + reactStrictMode: true, + + // These take effect in development and should match the redirects in netlify.toml + async redirects() { + return [ + { + source: '/', + destination: '/docs/introduction', + permanent: true + }, + { + source: '/docs', + destination: '/docs/introduction', + permanent: true + }, + { + source: '/community', + destination: '/docs/community', + permanent: true + } + ] + }, + + webpack: config => { + return { + ...config, + resolve: { + ...config.resolve, + alias: { + ...config.resolve.alias, + + // See site/module-stubs/README.md + cosmiconfig: path.resolve(__dirname, 'module-stubs/cosmiconfig.cjs'), + 'find-root': path.resolve(__dirname, 'module-stubs/find-root.cjs'), + resolve: path.resolve(__dirname, 'module-stubs/resolve.cjs') + } + } + } + } +}) diff --git a/site/package.json b/site/package.json index 61c31fd3a..58268edac 100644 --- a/site/package.json +++ b/site/package.json @@ -1,57 +1,41 @@ { "name": "emotion-site", + "version": "0.0.0", "private": true, - "description": "emotion site", - "version": "9.3.0", + "scripts": { + "build": "next build && next export", + "dev": "next dev", + "start": "next start", + "test:typescript": "exit 0" + }, "dependencies": { - "@babel/preset-env": "^7.13.10", - "@babel/preset-react": "^7.12.13", - "@babel/standalone": "^7.13.11", - "@mdx-js/mdx": "^1.6.22", - "@mdx-js/react": "^1.6.22", - "escape-goat": "^1.3.0", - "facepaint": "^1.1.2", - "favicons-webpack-plugin": "^0.0.9", - "gatsby": "^2.32.11", - "gatsby-image": "^2.2.7", - "gatsby-plugin-catch-links": "^2.1.2", - "gatsby-plugin-emotion": "^4.1.2", - "gatsby-plugin-google-analytics": "^2.1.4", - "gatsby-plugin-mdx": "^1.10.1", - "gatsby-plugin-netlify": "^2.11.1", - "gatsby-plugin-react-helmet": "^3.1.2", - "gatsby-plugin-sharp": "^2.14.4", - "gatsby-plugin-sitemap": "^2.2.3", - "gatsby-remark-autolink-headers": "^2.1.3", - "gatsby-remark-images": "^3.1.7", - "gatsby-remark-prismjs": "^3.3.3", - "gatsby-remark-smartypants": "^2.1.2", - "gatsby-source-filesystem": "^2.1.6", - "gatsby-transformer-remark": "^2.6.9", - "gatsby-transformer-sharp": "^2.12.1", - "graphql-type-json": "^0.2.4", - "normalize.css": "^8.0.0", - "open-color": "^1.5.1", + "@babel/standalone": "^7.18.7", + "@docsearch/css": "^3.1.1", + "@docsearch/react": "^3.1.1", + "@fortawesome/fontawesome-svg-core": "^6.1.1", + "@fortawesome/free-solid-svg-icons": "^6.1.1", + "@fortawesome/react-fontawesome": "^0.2.0", + "@next/bundle-analyzer": "^12.2.0", + "@types/babel__standalone": "^7.1.4", + "@types/js-yaml": "^4.0.5", + "@types/node": "^12.20.37", + "@types/prismjs": "^1.26.0", + "@types/react": "^18.0.9", + "@types/remark-prism": "^1.3.3", + "facepaint": "^1.2.1", + "gray-matter": "^4.0.3", + "js-yaml": "^4.1.0", + "next": "^12.2.0", + "next-mdx-remote": "^4.0.3", + "prism-react-renderer": "^1.3.5", "react": "16.14.0", "react-dom": "16.14.0", - "react-helmet": "^5.2.0", - "react-icons": "^2.2.7", - "react-live": "1.10.0", - "remark-frontmatter": "^1.1.0", - "remark-parse": "^4.0.0", - "styled-system": "^1.0.8", - "unified": "^6.1.6", - "unist-util-visit": "^1.2.0", - "webpack-bundle-analyzer": "3.3.2", - "worker-loader": "2.0.0" - }, - "keywords": [ - "gatsby" - ], - "license": "MIT", - "scripts": { - "build:site": "gatsby build", - "start:site": "gatsby develop", - "test:typescript": "exit 0" + "react-simple-code-editor": "^0.11.2", + "rehype-autolink-headings": "^6.1.1", + "rehype-slug": "^5.0.1", + "remark-prism": "^1.3.6", + "sharp": "^0.30.7", + "typescript": "^4.5.5", + "unist-util-visit": "^4.1.0" } } diff --git a/site/pages/404.tsx b/site/pages/404.tsx new file mode 100644 index 000000000..9e324370b --- /dev/null +++ b/site/pages/404.tsx @@ -0,0 +1,19 @@ +import Head from 'next/head' +import { ReactElement } from 'react' +import { styleConstants } from '../util' + +export default function NotFoundPage(): ReactElement { + const title = 'Page not found' + + return ( + <> + + {title} + +

{title}

+

+ You just hit a route that doesn't exist... the sadness. 😢 +

+ + ) +} diff --git a/site/pages/_app.tsx b/site/pages/_app.tsx new file mode 100644 index 000000000..28c1a4b5b --- /dev/null +++ b/site/pages/_app.tsx @@ -0,0 +1,21 @@ +import '../public/bootstrap-reboot.min.css' +import '@docsearch/css' +import Head from 'next/head' +import type { AppProps } from 'next/app' +import '../util/prism-customizations' +import { SiteHeader, GlobalStyles, Container } from '../components' + +export default function MyApp({ Component, pageProps }: AppProps) { + return ( + <> + + + + + + + + + + ) +} diff --git a/site/pages/docs/@emotion/[packageName].tsx b/site/pages/docs/@emotion/[packageName].tsx new file mode 100644 index 000000000..bcb54a18d --- /dev/null +++ b/site/pages/docs/@emotion/[packageName].tsx @@ -0,0 +1,54 @@ +import { GetStaticPaths, GetStaticPropsContext } from 'next' +import { serialize } from 'next-mdx-remote/serialize' +import remarkPrism from 'remark-prism' +import rehypeSlug from 'rehype-slug' +import rehypeAutolinkHeadings from 'rehype-autolink-headings' +import { docQueries } from '../../../queries' +import { + remarkFixLinks, + remarkResponsiveTables +} from '../../../util/remark-plugins' +import DocsPage from '../[slug]' + +export const getStaticPaths: GetStaticPaths = async () => { + return { + paths: docQueries.listPackageNames().map(packageName => ({ + params: { + packageName + } + })), + fallback: false + } +} + +export async function getStaticProps({ params }: GetStaticPropsContext) { + const packageName = params!.packageName as string + + const content = docQueries.getReadme(packageName) + + // mdxOptions is duplicated in an attempt to prevent the client-side bundle + // from containing any mdx/remark JS + + // READMEs should not contain live code blocks + const mdx = await serialize(content, { + mdxOptions: { + remarkPlugins: [remarkPrism, remarkFixLinks, remarkResponsiveTables], + + // rehypeSlug must come first + rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings] + } + }) + + const fullPackageName = `@emotion/${packageName}` + + return { + props: { + slug: fullPackageName, + title: fullPackageName, + mdx, + docGroups: docQueries.listGroups() + } + } +} + +export default DocsPage diff --git a/site/pages/docs/[slug].tsx b/site/pages/docs/[slug].tsx new file mode 100644 index 000000000..4463144be --- /dev/null +++ b/site/pages/docs/[slug].tsx @@ -0,0 +1,141 @@ +import { + GetStaticPaths, + GetStaticPropsContext, + InferGetStaticPropsType +} from 'next' +import Head from 'next/head' +import { ReactElement } from 'react' +import { serialize } from 'next-mdx-remote/serialize' +import { MDXRemote } from 'next-mdx-remote' +import remarkPrism from 'remark-prism' +import rehypeSlug from 'rehype-slug' +import rehypeAutolinkHeadings from 'rehype-autolink-headings' +import { DocWrapper, ResponsiveTable, Title } from '../../components' +import { docQueries } from '../../queries' +import { + remarkFixLinks, + remarkResponsiveTables +} from '../../util/remark-plugins' +import { mediaQueries, styleConstants } from '../../util' +import { + remarkLiveEditor, + EmotionLiveEditor +} from '../../components/live-editor' + +export const getStaticPaths: GetStaticPaths = async () => { + return { + paths: docQueries.listMdxSlugs().map(slug => ({ + params: { + slug + } + })), + fallback: false + } +} + +export async function getStaticProps({ params }: GetStaticPropsContext) { + const slug = params!.slug as string + + const { title, content } = docQueries.getMdx(slug) + + // mdxOptions is duplicated in an attempt to prevent the client-side bundle + // from containing any mdx/remark JS. This is recommended by the + // next-mdx-remote README. + const mdx = await serialize(content, { + mdxOptions: { + // remarkLiveEditor must come before remarkPrism + remarkPlugins: [ + remarkLiveEditor, + remarkPrism, + remarkFixLinks, + remarkResponsiveTables + ], + + // rehypeSlug must come first + rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings] + } + }) + + return { + props: { + slug, + title, + mdx, + docGroups: docQueries.listGroups() + } + } +} + +interface DocTitleProps { + title: string + slug: string +} + +function DocTitle({ title, slug }: DocTitleProps): ReactElement { + let editUrl + + if (slug.startsWith('@emotion/')) { + const packageName = slug.replace('@emotion/', '') + editUrl = `https://github.com/emotion-js/emotion/edit/main/packages/${packageName}/README.md` + } else { + editUrl = `https://github.com/emotion-js/emotion/edit/main/docs/${slug}.mdx` + } + + return ( +
+ + {title} + + + ✏️ Edit this page + +
+ ) +} + +export default function DocsPage({ + slug, + title, + mdx, + docGroups +}: InferGetStaticPropsType): ReactElement { + return ( + <> + + Emotion – {title} + + + + + + + ) +} diff --git a/site/plugins/gatsby-plugin-emotion-next-compat/gatsby-browser.js b/site/plugins/gatsby-plugin-emotion-next-compat/gatsby-browser.js deleted file mode 100644 index ceb042c3b..000000000 --- a/site/plugins/gatsby-plugin-emotion-next-compat/gatsby-browser.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow -import * as React from 'react' -import { cache } from '@emotion/css' -import { CacheProvider } from '@emotion/react' - -export const wrapRootElement = ({ element }: { element: React.Node }) => { - return {element} -} diff --git a/site/plugins/gatsby-plugin-emotion-next-compat/gatsby-ssr.js b/site/plugins/gatsby-plugin-emotion-next-compat/gatsby-ssr.js deleted file mode 100644 index 13162e0f6..000000000 --- a/site/plugins/gatsby-plugin-emotion-next-compat/gatsby-ssr.js +++ /dev/null @@ -1,26 +0,0 @@ -// @flow -import * as React from 'react' -import { renderToString } from 'react-dom/server' -import { extractCritical } from '@emotion/server' -import { cache } from '@emotion/css' -import { CacheProvider } from '@emotion/react' - -export const replaceRenderer = ({ - replaceBodyHTMLString, - bodyComponent, - setHeadComponents -}: *) => { - let { html, ids, css } = extractCritical( - renderToString({bodyComponent}) - ) - setHeadComponents([ -