Browse Source

Initial check-in of new React docs and website

Co-authored-by: Dan Abramov <dan.abramov@me.com>
Co-authored-by: Sylwia Vargas <sylwia.vargas@gmail.com>
Co-authored-by: Dan Lebowitz <dan.lebo@me.com>
Co-authored-by: Razvan Gradinar <grazvan@fb.com>
Co-authored-by: Jared Palmer <jared@palmer.net>
Co-authored-by: Dane Grant <danecando@gmail.com>
Co-authored-by: Dustin Goodman <dustin.s.goodman@gmail.com>
Co-authored-by: Rick Hanlon <rickhanlonii@gmail.com>
Co-authored-by: Maggie Appleton <maggie.fm.appleton@gmail.com>
Co-authored-by: Alex Moldovan <alex.n.moldovan@gmail.com>
Co-authored-by: Ives van Hoorne <ives.v.h@gmail.com>
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
Co-authored-by: Dmitri Pavlutin <dpavlutin@gmail.com>
main
Rachel Nabors 3 years ago
committed by Dan Abramov
parent
commit
981db3397e
  1. 3
      .eslintignore
  2. 1
      .flowconfig
  3. 24
      .github/workflows/nodejs.yml
  4. 2
      beta/.env.development
  5. 2
      beta/.env.production
  6. 3
      beta/.eslintignore
  7. 18
      beta/.eslintrc
  8. 34
      beta/.gitignore
  9. 2
      beta/.prettierignore
  10. 7
      beta/.prettierrc
  11. 135
      beta/CONTRIBUTING.md
  12. 70
      beta/README.md
  13. 88
      beta/colors.js
  14. 4950
      beta/illustrations/import-export.ai
  15. 1564
      beta/illustrations/jsx.ai
  16. 6
      beta/next-env.d.ts
  17. 51
      beta/next.config.js
  18. 92
      beta/package.json
  19. 35
      beta/patches/@next+plugin-google-analytics+9.5.2.patch
  20. 30
      beta/plugins/markdownToHtml.js
  21. 28
      beta/plugins/md-layout-loader.js
  22. 52
      beta/plugins/remark-header-custom-ids.js
  23. 21
      beta/plugins/remark-smartypants.js
  24. 13
      beta/plugins/sandbox-templates/cra/package.json
  25. 5
      beta/plugins/sandbox-templates/cra/src/App.js
  26. 13
      beta/plugins/sandbox-templates/cra/src/index.js
  27. 3
      beta/plugins/sandbox-templates/cra/src/styles.css
  28. 20
      beta/postcss.config.js
  29. 69
      beta/scripts/generateBlogIndex.js
  30. 79
      beta/scripts/generateHeadingIDs.js
  31. 46
      beta/scripts/generateRSS.js
  32. 50
      beta/scripts/migrations/migrateBlogPosts.js
  33. 35
      beta/scripts/migrations/migratePermalinks.js
  34. 117
      beta/scripts/migrations/migrateRedirects.js
  35. 130
      beta/src/authors.json
  36. 1400
      beta/src/blogIndex.json
  37. 207
      beta/src/blogIndexRecent.json
  38. 45
      beta/src/components/Breadcrumbs.tsx
  39. 49
      beta/src/components/Button.tsx
  40. 46
      beta/src/components/ButtonLink.tsx
  41. 93
      beta/src/components/DocsFooter.tsx
  42. 20
      beta/src/components/ExternalLink.tsx
  43. 30
      beta/src/components/Icon/IconArrow.tsx
  44. 33
      beta/src/components/Icon/IconArrowSmall.tsx
  45. 42
      beta/src/components/Icon/IconChevron.tsx
  46. 28
      beta/src/components/Icon/IconClose.tsx
  47. 26
      beta/src/components/Icon/IconCodeBlock.tsx
  48. 26
      beta/src/components/Icon/IconDeepDive.tsx
  49. 24
      beta/src/components/Icon/IconError.tsx
  50. 22
      beta/src/components/Icon/IconFacebookCircle.tsx
  51. 22
      beta/src/components/Icon/IconGitHub.tsx
  52. 26
      beta/src/components/Icon/IconGotcha.tsx
  53. 28
      beta/src/components/Icon/IconHamburger.tsx
  54. 26
      beta/src/components/Icon/IconHint.tsx
  55. 22
      beta/src/components/Icon/IconInstagram.tsx
  56. 44
      beta/src/components/Icon/IconNavArrow.tsx
  57. 25
      beta/src/components/Icon/IconNewPage.tsx
  58. 26
      beta/src/components/Icon/IconNote.tsx
  59. 25
      beta/src/components/Icon/IconRestart.tsx
  60. 29
      beta/src/components/Icon/IconRss.tsx
  61. 24
      beta/src/components/Icon/IconSearch.tsx
  62. 26
      beta/src/components/Icon/IconSolution.tsx
  63. 26
      beta/src/components/Icon/IconTerminal.tsx
  64. 24
      beta/src/components/Icon/IconTwitter.tsx
  65. 28
      beta/src/components/Icon/IconWarning.tsx
  66. 183
      beta/src/components/Layout/Footer.tsx
  67. 26
      beta/src/components/Layout/LayoutAPI.tsx
  68. 26
      beta/src/components/Layout/LayoutHome.tsx
  69. 24
      beta/src/components/Layout/LayoutLearn.tsx
  70. 117
      beta/src/components/Layout/LayoutPost.tsx
  71. 154
      beta/src/components/Layout/MarkdownPage.tsx
  72. 86
      beta/src/components/Layout/Nav/MobileNav.tsx
  73. 193
      beta/src/components/Layout/Nav/Nav.tsx
  74. 39
      beta/src/components/Layout/Nav/NavButtonLink.tsx
  75. 38
      beta/src/components/Layout/Nav/NavLink.tsx
  76. 5
      beta/src/components/Layout/Nav/index.tsx
  77. 40
      beta/src/components/Layout/Page.tsx
  78. 96
      beta/src/components/Layout/Sidebar/Sidebar.tsx
  79. 57
      beta/src/components/Layout/Sidebar/SidebarButton.tsx
  80. 84
      beta/src/components/Layout/Sidebar/SidebarLink.tsx
  81. 155
      beta/src/components/Layout/Sidebar/SidebarRouteTree.tsx
  82. 8
      beta/src/components/Layout/Sidebar/index.tsx
  83. 81
      beta/src/components/Layout/Toc.tsx
  84. 37
      beta/src/components/Layout/useMediaQuery.tsx
  85. 124
      beta/src/components/Layout/useRouteMeta.tsx
  86. 83
      beta/src/components/Layout/useTocHighlight.tsx
  87. 49
      beta/src/components/Layout/useTwitter.tsx
  88. 28
      beta/src/components/Logo.tsx
  89. 127
      beta/src/components/MDX/APIAnatomy.tsx
  90. 230
      beta/src/components/MDX/Challenges/Challenges.tsx
  91. 137
      beta/src/components/MDX/Challenges/Navigation.tsx
  92. 14
      beta/src/components/MDX/Challenges/index.tsx
  93. 7
      beta/src/components/MDX/CodeBlock/CodeBlock.module.css
  94. 179
      beta/src/components/MDX/CodeBlock/CodeBlock.tsx
  95. 7
      beta/src/components/MDX/CodeBlock/index.tsx
  96. 46
      beta/src/components/MDX/CodeDiagram.tsx
  97. 82
      beta/src/components/MDX/ConsoleBlock.tsx
  98. 25
      beta/src/components/MDX/Convention.tsx
  99. 66
      beta/src/components/MDX/ExpandableCallout.tsx
  100. 85
      beta/src/components/MDX/ExpandableExample.tsx

3
.eslintignore

@ -1,5 +1,8 @@
node_modules/*
# Skip beta
beta/*
# Ignore markdown files and examples
content/*

1
.flowconfig

@ -1,5 +1,6 @@
[ignore]
<PROJECT_ROOT>/beta/.*
<PROJECT_ROOT>/content/.*
<PROJECT_ROOT>/node_modules/.*
<PROJECT_ROOT>/public/.*

24
.github/workflows/nodejs.yml

@ -0,0 +1,24 @@
name: Build
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
lint:
runs-on: ubuntu-latest
name: Lint on node 12.x and ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Install deps and build (with cache)
uses: bahmutov/npm-install@v1
- name: Lint codebase
run: yarn ci-check

2
beta/.env.development

@ -0,0 +1,2 @@
NEXT_PUBLIC_HJ_SITE_ID = 2411683
NEXT_PUBLIC_HJ_SITE_V = 6

2
beta/.env.production

@ -0,0 +1,2 @@
NEXT_PUBLIC_HJ_SITE_ID = 2411651
NEXT_PUBLIC_HJ_SITE_V = 6

3
beta/.eslintignore

@ -0,0 +1,3 @@
scripts
plugins
next.config.js

18
beta/.eslintrc

@ -0,0 +1,18 @@
{
"extends": ["react-app", "plugin:jsx-a11y/recommended"],
"plugins": ["jsx-a11y"],
"overrides": [
{
"files": ["**/*.ts?(x)"],
"rules": {
"jsx-a11y/anchor-is-valid": 0
}
}
],
"env": {
"node": true,
"commonjs": true,
"browser": true,
"es6": true
}
}

34
beta/.gitignore

@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

2
beta/.prettierignore

@ -0,0 +1,2 @@
src/pages/docs/**/*.md
src/pages/blog/**/*.md

7
beta/.prettierrc

@ -0,0 +1,7 @@
{
"bracketSpacing": false,
"singleQuote": true,
"jsxBracketSameLine": true,
"trailingComma": "es5",
"printWidth": 80
}

135
beta/CONTRIBUTING.md

@ -0,0 +1,135 @@
# Contributing
Thank you for your interest in contributing to the React Docs!
## Code of Conduct
Facebook has adopted a Code of Conduct that we expect project
participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct)
so that you can understand what actions will and will not be tolerated.
## Technical Writing Tips
This is a [good summary](https://medium.com/@kvosswinkel/coding-like-a-journalist-ee52360a16bc) for things to keep in mind when writing technical docs.
## Guidelines for Text
**Different sections intentionally have different styles.**
The documentation is divided into sections to cater to different learning styles and use cases. When editing an article, try to match the surrounding text in tone and style. When creating a new article, try to match the tone of the other articles in the same section. Learn about the motivation behind each section below.
**[Learn React](https://beta.reactjs.org/learn)** is designed to introduce fundamental concepts in a step-by-step way. Each individual article in Learn React builds on the knowledge from the previous ones, so make sure not to add any "cyclical dependencies" between them. It is important that the reader can start with the first article and work their way to the last Learn React article without ever having to "look ahead" for a definition. This explains some ordering choices (e.g. that state is explained before events, or that "thinking in React" doesn't use refs). Learn React also serves as a reference manual for React concepts, so it is important to be very strict about their definitions and relationships between them.
**[API Reference](https://reactjs.org/reference)** is organized by APIs rather than concepts. It is intended to be exhaustive. Any corner cases or recommendations that were skipped for brevity in Learn React should be mentioned in the reference documentation for the corresponding APIs.
**Try to follow your own instructions.**
When writing step-by-step instructions (e.g. how to install something), try to forget everything you know about the topic, and actually follow the instructions you wrote, a single step at time. Often you will discover that there is implicit knowledge that you forgot to mention, or that there are missing or out-of-order steps in the instructions. Bonus points for getting *somebody else* to follow the steps and watching what they struggle with. Often it would be something very simple that you have not anticipated.
## Guidelines for Code Examples
### Syntax
#### Prefer JSX to `createElement`.
Ignore this if you're specifically describing `createElement`.
#### Use `const` where possible, otherwise `let`. Don't use `var`.
Ignore this if you're specifically writing about ES5.
#### Don't use ES6 features when equivalent ES5 features have no downsides.
Remember that ES6 is still new to a lot of people. While we use it in many places (`const` / `let`, classes, arrow functions), if the equivalent ES5 code is just as straightforward and readable, consider using it.
In particular, you should prefer named `function` declarations over `const myFunction = () => ...` arrows for top-level functions. However, you *should* use arrow functions where they provide a tangible improvement (such as preserving `this` context inside a component). Consider both sides of the tradeoff when deciding whether to use a new feature.
#### Don't use features that aren't standardized yet.
For example, **don't** write this:
```js
class MyComponent extends React.Component {
state = {value: ''};
handleChange = (e) => {
this.setState({value: e.target.value});
};
}
```
Instead, **do** write this:
```js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: ''};
}
handleChange(e) {
this.setState({value: e.target.value});
}
}
```
Ignore this rule if you're specifically describing an experimental proposal. Make sure to mention its experimental nature in the code and in the surrounding text.
### Style
- Use semicolons.
- No space between function names and parens (`method() {}` not `method () {}`).
- When in doubt, use the default style favored by [Prettier](https://prettier.io/playground/).
### Highlighting
Use `js` as the highlighting language in Markdown code blocks:
````
```js
// code
```
````
Sometimes you'll see blocks with numbers.
They tell the website to highlight specific lines.
You can highlight a single line:
````
```js {2}
function hello() {
// this line will get highlighted
}
```
````
A range of lines:
````
```js {2-4}
function hello() {
// these lines
// will get
// highlighted
}
```
````
Or even multiple ranges:
````
```js {2-4,6}
function hello() {
// these lines
// will get
// highlighted
console.log('hello');
// also this one
console.log('there');
}
```
````
Be mindful that if you move some code in an example with highlighting, you also need to update the highlighting.
Don't be afraid to often use highlighting! It is very valuable when you need to focus the reader's attention on a particular detail that's easy to miss.

70
beta/README.md

@ -0,0 +1,70 @@
# reactjs.org
This repo contains the source code and documentation powering [reactjs.org](https://reactjs.org/).
## Getting started
### Prerequisites
1. Git
1. Node: any 12.x version starting with v12.0.0 or greater
1. Yarn: See [Yarn website for installation instructions](https://yarnpkg.com/lang/en/docs/install/)
1. A fork of the repo (for any contributions)
1. A clone of the [reactjs.org repo](https://github.com/reactjs/reactjs.org) on your local machine
### Installation
1. `cd reactjs.org` to go into the project root
1. `yarn` to install the website's npm dependencies
### Running locally
1. `yarn dev` to start the development server (powered by [Next.js](https://nextjs.org/))
1. `open http://localhost:3000` to open the site in your favorite browser
## Contributing
### Guidelines
The documentation is divided into several sections with a different tone and purpose. If you plan to write more than a few sentences, you might find it helpful to get familiar with the [contributing guidelines](https://github.com/reactjs/reactjs.org/blob/main/CONTRIBUTING.md#guidelines-for-text) for the appropriate sections.
### Create a branch
1. `git checkout main` from any folder in your local `reactjs.org` repository
1. `git pull origin main` to ensure you have the latest main code
1. `git checkout -b the-name-of-my-branch` (replacing `the-name-of-my-branch` with a suitable name) to create a branch
### Make the change
1. Follow the ["Running locally"](#running-locally) instructions
1. Save the files and check in the browser
1. Changes to React components in `src` will hot-reload
1. Changes to markdown files in `content` will hot-reload
1. If working with plugins, you may need to remove the `.cache` directory and restart the server
### Test the change
1. If possible, test any visual changes in all latest versions of common browsers, on both desktop and mobile.
1. Run `yarn check-all` from the project root. (This will run Prettier, ESLint, and Flow.)
### Push it
1. `git add -A && git commit -m "My message"` (replacing `My message` with a commit message, such as `Fix header logo on Android`) to stage and commit your changes
1. `git push my-fork-name the-name-of-my-branch`
1. Go to the [reactjs.org repo](https://github.com/reactjs/reactjs.org) and you should see recently pushed branches.
1. Follow GitHub's instructions.
1. If possible, include screenshots of visual changes. A [Netlify](https://www.netlify.com/) build will also be automatically created once you make your PR so other people can see your change.
## Translation
If you are interested in translating `reactjs.org`, please see the current translation efforts at [translations.reactjs.org](https://translations.reactjs.org/).
If your language does not have a translation and you would like to create one, please follow the instructions at [reactjs.org Translations](https://github.com/reactjs/reactjs.org-translation#translating-reactjsorg).
## Troubleshooting
- `yarn reset` to clear the local cache
## License
Content submitted to [reactjs.org](https://reactjs.org/) is CC-BY-4.0 licensed, as found in the [LICENSE-DOCS.md](https://github.com/open-source-explorer/reactjs.org/blob/master/LICENSE-DOCS.md) file.

88
beta/colors.js

@ -0,0 +1,88 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
module.exports = {
// Text colors
primary: '#23272F', // gray-90
'primary-dark': '#F6F7F9', // gray-5
secondary: '#404756', // gray-70
'secondary-dark': '#EBECF0', // gray-10
link: '#087EA4', // blue-50
'link-dark': '#149ECA', // blue-40
syntax: '#EBECF0', // gray-10
wash: '#FFFFFF',
'wash-dark': '#23272F', // gray-90
card: '#F6F7F9', // gray-05
'card-dark': '#343A46', // gray-80
highlight: '#E6F7FF', // blue-10
'highlight-dark': 'rgba(88,175,223,.1)',
border: '#EBECF0', // gray-10
'border-dark': '#343A46', // gray-80
'secondary-button': '#EBECF0', // gray-10
'secondary-button-dark': '#404756', // gray-70
// Gray
'gray-90': '#23272F',
'gray-80': '#343A46',
'gray-70': '#404756',
'gray-60': '#4E5769',
'gray-50': '#5E687E', // unused
'gray-40': '#78839B',
'gray-30': '#99A1B3',
'gray-20': '#BCC1CD',
'gray-10': '#EBECF0',
'gray-5': '#F6F7F9',
// Blue
'blue-60': '#045975',
'blue-50': '#087EA4',
'blue-40': '#149ECA', // Brand Blue
'blue-30': '#58C4DC', // unused
'blue-20': '#ABE2ED',
'blue-10': '#E6F7FF', // todo: doesn't match illustrations
'blue-5': '#E6F6FA',
// Yellow
'yellow-60': '#B65700',
'yellow-50': '#C76A15',
'yellow-40': '#DB7D27', // unused
'yellow-30': '#FABD62', // unused
'yellow-20': '#FCDEB0', // unused
'yellow-10': '#FDE7C7',
'yellow-5': '#FEF5E7',
// Purple
'purple-60': '#2B3491', // unused
'purple-50': '#575FB7',
'purple-40': '#6B75DB',
'purple-30': '#8891EC',
'purple-20': '#C3C8F5', // unused
'purple-10': '#E7E9FB',
'purple-5': '#F3F4FD',
// Green
'green-60': '#2B6E62',
'green-50': '#388F7F',
'green-40': '#44AC99',
'green-30': '#7FCCBF',
'green-20': '#ABDED5',
'green-10': '#E5F5F2',
'green-5': '#F4FBF9',
// RED
'red-60': '#712D28',
'red-50': '#A6423A', // unused
'red-40': '#C1554D',
'red-30': '#D07D77',
'red-20': '#E5B7B3', // unused
'red-10': '#F2DBD9', // unused
'red-5': '#FAF1F0',
// MISC
'code-block': '#99a1b30f', // gray-30 @ 6%
'gradient-blue': '#58C4DC', // Only used for the landing gradient for now.
github: {
highlight: '#fffbdd',
},
};

4950
beta/illustrations/import-export.ai

File diff suppressed because one or more lines are too long

1564
beta/illustrations/jsx.ai

File diff suppressed because one or more lines are too long

6
beta/next-env.d.ts

@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

51
beta/next.config.js

@ -0,0 +1,51 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
const path = require('path');
const {remarkPlugins} = require('./plugins/markdownToHtml');
const redirects = require('./src/redirects.json');
module.exports = {
pageExtensions: ['jsx', 'js', 'ts', 'tsx', 'mdx', 'md'],
experimental: {
plugins: true,
reactMode: 'concurrent',
scrollRestoration: true,
},
async redirects() {
return redirects.redirects;
},
env: {
// @todo Remove when https://github.com/vercel/next.js/pull/16529 lands
GA_TRACKING_ID: 'XXXX',
NEXT_PUBLIC_GA_TRACKING_ID: 'XXX',
},
rewrites() {
return [
{
source: '/feed.xml',
destination: '/_next/static/feed.xml',
},
];
},
webpack: (config, {dev, isServer, ...options}) => {
// Add our custom markdown loader in order to support frontmatter
// and layout
config.module.rules.push({
test: /.mdx?$/, // load both .md and .mdx files
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: {
remarkPlugins,
},
},
path.join(__dirname, './plugins/md-layout-loader'),
],
});
return config;
},
};

92
beta/package.json

@ -0,0 +1,92 @@
{
"name": "react-website",
"version": "1.0.0",
"private": true,
"license": "CC",
"scripts": {
"dev": "next",
"build": "next build && node ./scripts/generateRSS.js",
"build-ci": "git clean -df && next build && next export && node ./scripts/generateRSS.js && rm -rf public/* && cp -r out/* public/",
"lint": "eslint 'src/**/*.{js,ts,tsx}'",
"format:source": "prettier --config .prettierrc --write \"{plugins,src}/**/*.{js,ts,jsx,tsx}\"",
"nit:source": "prettier --config .prettierrc --list-different \"{plugins,src}/**/*.{js,ts,jsx,tsx}\"",
"prettier": "yarn format:source",
"prettier:diff": "yarn nit:source",
"generate-ids": "node scripts/generateHeadingIDs.js src/pages/docs && node scripts/generateHeadingIDs.js src/pages/blog",
"ci-check": "npm-run-all prettier:diff --parallel lint tsc",
"tsc": "tsc --noEmit",
"start": "next start"
},
"dependencies": {
"@codesandbox/sandpack-react": "^0.1.15",
"@docsearch/react": "3.0.0-alpha.41",
"@docsearch/css": "3.0.0-alpha.41",
"@headlessui/react": "^1.3.0",
"@mdx-js/mdx": "^1.6.16",
"@mdx-js/react": "^1.6.16",
"@mdx-js/tag": "^0.20.3",
"@next/plugin-google-analytics": "^10.0.6",
"add": "^2.0.6",
"body-scroll-lock": "^3.1.3",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.3.1",
"date-fns": "^2.16.1",
"debounce": "^1.2.1",
"github-slugger": "^1.3.0",
"hex2rgba": "^0.0.1",
"next": "^11.1.2",
"parse-numeric-range": "^1.2.0",
"react": "18.0.0-alpha-930c9e7ee-20211015",
"react-collapsed": "3.1.0",
"react-dom": "18.0.0-alpha-930c9e7ee-20211015",
"scroll-into-view-if-needed": "^2.2.25"
},
"devDependencies": {
"@mdx-js/loader": "^1.6.16",
"@types/body-scroll-lock": "^2.6.1",
"@types/classnames": "^2.2.10",
"@types/github-slugger": "^1.3.0",
"@types/mdx-js__react": "^1.5.2",
"@types/node": "^14.6.4",
"@types/parse-numeric-range": "^0.0.1",
"@types/react": "^16.9.46",
"@types/react-dom": "^16.9.8",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"asyncro": "^3.0.0",
"autoprefixer": "^10.3.1",
"babel-eslint": "10.x",
"eslint": "6.x",
"eslint-config-react-app": "^5.2.1",
"eslint-plugin-flowtype": "4.x",
"eslint-plugin-import": "2.x",
"eslint-plugin-jsx-a11y": "6.x",
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "2.x",
"fs-extra": "^9.0.1",
"globby": "^11.0.1",
"gray-matter": "^4.0.2",
"mdast-util-to-string": "^1.1.0",
"npm-run-all": "^4.1.5",
"patch-package": "^6.2.2",
"postcss": "^8.3.6",
"postcss-flexbugs-fixes": "4.2.1",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.1.1",
"reading-time": "^1.2.0",
"remark": "^12.0.1",
"remark-external-links": "^7.0.0",
"remark-html": "^12.0.0",
"remark-images": "^2.0.0",
"remark-unwrap-images": "^2.0.0",
"retext": "^7.0.1",
"retext-smartypants": "^4.0.0",
"rss": "^1.2.2",
"tailwindcss": "^2.2.7",
"typescript": "^4.0.2",
"unist-util-visit": "^2.0.3"
},
"engines": {
"node": ">=12.x"
}
}

35
beta/patches/@next+plugin-google-analytics+9.5.2.patch

@ -0,0 +1,35 @@
diff --git a/node_modules/@next/plugin-google-analytics/src/document-head-tags-server.js b/node_modules/@next/plugin-google-analytics/src/document-head-tags-server.js
index d10a05b..b27a7c4 100644
--- a/node_modules/@next/plugin-google-analytics/src/document-head-tags-server.js
+++ b/node_modules/@next/plugin-google-analytics/src/document-head-tags-server.js
@@ -5,7 +5,7 @@ export default async function headTags() {
<>
<script
async
- src={`https://www.googletagmanager.com/gtag/js?id=${process.env.GA_TRACKING_ID}`}
+ src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
@@ -13,7 +13,7 @@ export default async function headTags() {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
- gtag('config', '${process.env.GA_TRACKING_ID}');
+ gtag('config', '${process.env.NEXT_PUBLIC_GA_TRACKING_ID}');
`,
}}
/>
diff --git a/node_modules/@next/plugin-google-analytics/src/on-init-client.js b/node_modules/@next/plugin-google-analytics/src/on-init-client.js
index 094b2c9..e250a31 100644
--- a/node_modules/@next/plugin-google-analytics/src/on-init-client.js
+++ b/node_modules/@next/plugin-google-analytics/src/on-init-client.js
@@ -1,7 +1,7 @@
export default async function initClient({ router }) {
router.events.on('routeChangeComplete', (url) => {
setTimeout(() => {
- window.gtag('config', process.env.GA_TRACKING_ID, {
+ window.gtag('config', process.env.NEXT_PUBLIC_GA_TRACKING_ID, {
page_location: url,
page_title: document.title,
})

30
beta/plugins/markdownToHtml.js

@ -0,0 +1,30 @@
const remark = require('remark');
const externalLinks = require('remark-external-links'); // Add _target and rel to external links
const customHeaders = require('./remark-header-custom-ids'); // Custom header id's for i18n
const images = require('remark-images'); // Improved image syntax
const unrwapImages = require('remark-unwrap-images'); // Removes <p> wrapper around images
const smartyPants = require('./remark-smartypants'); // Cleans up typography
const html = require('remark-html');
module.exports = {
remarkPlugins: [
externalLinks,
customHeaders,
images,
unrwapImages,
smartyPants,
],
markdownToHtml,
};
async function markdownToHtml(markdown) {
const result = await remark()
.use(externalLinks)
.use(customHeaders)
.use(images)
.use(unrwapImages)
.use(smartyPants)
.use(html)
.process(markdown);
return result.toString();
}

28
beta/plugins/md-layout-loader.js

@ -0,0 +1,28 @@
const fm = require('gray-matter');
// Makes mdx in next.js suck less by injecting necessary exports so that
// the docs are still readable on github.
//
// Layout component for a .mdx or .md page can be specfied in the frontmatter.
// This plugin assumes that the layout file and named export are the same. This
// easily changed by modifying the string below.
//
// All metadata can be written in yaml front matter. It will be passed to the
// layout component as `meta` prop.
//
// (Shamelessly stolen from Expo.io docs)
// @see https://github.com/expo/expo/blob/master/docs/common/md-loader.js
module.exports = async function (src) {
const callback = this.async();
const {content, data} = fm(src);
const layout = data.layout || 'Learn';
const code =
`import withLayout from 'components/Layout/Layout${layout}';
export default withLayout(${JSON.stringify(data)})
` + content;
return callback(null, code);
};

52
beta/plugins/remark-header-custom-ids.js

@ -0,0 +1,52 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*/
/*!
* Based on 'gatsby-remark-autolink-headers'
* Original Author: Kyle Mathews <mathews.kyle@gmail.com>
* Updated by Jared Palmer;
* Copyright (c) 2015 Gatsbyjs
*/
const toString = require('mdast-util-to-string');
const visit = require('unist-util-visit');
const slugs = require('github-slugger')();
function patch(context, key, value) {
if (!context[key]) {
context[key] = value;
}
return context[key];
}
const svgIcon = `<svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>`;
module.exports = ({
icon = svgIcon,
className = `anchor`,
maintainCase = false,
} = {}) => {
slugs.reset();
return function transformer(tree) {
visit(tree, 'heading', (node) => {
// Support custom-id syntax.
const rawHeader = toString(node);
const match = /^.+(\s*\{#([a-z0-9\-_]+?)\}\s*)$/.exec(rawHeader);
const id = match ? match[2] : slugs.slug(rawHeader, maintainCase);
if (match) {
// Remove the custom ID part from the text node.
const lastNode = node.children[node.children.length - 1];
lastNode.value = lastNode.value.replace(match[1], '');
}
const data = patch(node, 'data', {});
patch(data, 'id', id);
patch(data, 'htmlAttributes', {});
patch(data, 'hProperties', {});
patch(data.htmlAttributes, 'id', id);
patch(data.hProperties, 'id', id);
});
};
};

21
beta/plugins/remark-smartypants.js

@ -0,0 +1,21 @@
const visit = require('unist-util-visit');
const retext = require('retext');
const smartypants = require('retext-smartypants');
function check(parent) {
if (parent.tagName === 'script') return false;
if (parent.tagName === 'style') return false;
return true;
}
module.exports = function (options) {
const processor = retext().use(smartypants, options);
function transformer(tree) {
visit(tree, 'text', (node, index, parent) => {
if (check(parent)) node.value = String(processor.processSync(node.value));
});
}
return transformer;
};

13
beta/plugins/sandbox-templates/cra/package.json

@ -0,0 +1,13 @@
{
"private": true,
"description": "React example starter project",
"dependencies": {
"react": "17",
"react-dom": "17",
"react-scripts": "4.0.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
}
}

5
beta/plugins/sandbox-templates/cra/src/App.js

@ -0,0 +1,5 @@
import * as React from 'react';
export default function App() {
return <h1>Hello, world</h1>;
}

13
beta/plugins/sandbox-templates/cra/src/index.js

@ -0,0 +1,13 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import './styles.css';
import App from './App';
const rootElement = document.getElementById('root');
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);

3
beta/plugins/sandbox-templates/cra/src/styles.css

@ -0,0 +1,3 @@
body {
font-family: sans-serif;
}

20
beta/postcss.config.js

@ -0,0 +1,20 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-flexbugs-fixes': {},
'postcss-preset-env': {
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
features: {
'custom-properties': false,
},
},
},
}

69
beta/scripts/generateBlogIndex.js

@ -0,0 +1,69 @@
const fs = require('fs-extra');
const path = require('path');
const fm = require('gray-matter');
const globby = require('globby');
const parseISO = require('date-fns/parseISO');
const readingTime = require('reading-time');
const {markdownToHtml} = require('../plugins/markdownToHtml');
/**
* This looks at the ./src/pages/blog directory and creates a route manifest that can be used
* in the sidebar and footers, and (in theory) category and author pages.
*
* For now, the blog manifest is a big array in reverse chronological order.
*/
Promise.resolve()
.then(async () => {
const routes = [];
const blogPosts = await globby('src/pages/blog/**/*.md');
for (let postpath of blogPosts) {
const [year, month, day, title] = postpath
.replace('src/pages/blog/', '')
.split('/');
const rawStr = await fs.readFile(postpath, 'utf8');
const {data, excerpt, content} = fm(rawStr, {
excerpt: function firstLine(file, options) {
file.excerpt = file.content.split('\n').slice(0, 2).join(' ');
},
});
const rendered = await markdownToHtml(excerpt.trimLeft().trim());
routes.unshift({
path: postpath.replace('src/pages', ''),
date: [year, month, day].join('-'),
title: data.title,
author: data.author,
excerpt: rendered,
readingTime: readingTime(content).text,
});
}
const sorted = routes.sort((post1, post2) =>
parseISO(post1.date) > parseISO(post2.date) ? -1 : 1
);
const blogManifest = {
routes: sorted,
};
const blogRecentSidebar = {
routes: [
{
title: 'Recent Posts',
path: '/blog',
heading: true,
routes: sorted.slice(0, 25),
},
],
};
await fs.writeFile(
path.resolve('./src/blogIndex.json'),
JSON.stringify(blogManifest, null, 2)
);
await fs.writeFile(
path.resolve('./src/blogIndexRecent.json'),
JSON.stringify(blogRecentSidebar, null, 2)
);
})
.catch(console.error);

79
beta/scripts/generateHeadingIDs.js

@ -0,0 +1,79 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*/
const fs = require('fs');
const GithubSlugger = require('github-slugger');
function walk(dir) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(function (file) {
file = dir + '/' + file;
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(walk(file));
} else {
/* Is a file */
results.push(file);
}
});
return results;
}
function stripLinks(line) {
return line.replace(/\[([^\]]+)\]\([^)]+\)/, (match, p1) => p1);
}
function addHeaderID(line, slugger) {
// check if we're a header at all
if (!line.startsWith('#')) {
return line;
}
// check if it already has an id
if (/\{#[^}]+\}/.test(line)) {
return line;
}
const headingText = line.slice(line.indexOf(' ')).trim();
const headingLevel = line.slice(0, line.indexOf(' '));
return `${headingLevel} ${headingText} {#${slugger.slug(
stripLinks(headingText)
)}}`;
}
function addHeaderIDs(lines) {
// Sluggers should be per file
const slugger = new GithubSlugger();
let inCode = false;
const results = [];
lines.forEach((line) => {
// Ignore code blocks
if (line.startsWith('```')) {
inCode = !inCode;
results.push(line);
return;
}
if (inCode) {
results.push(line);
return;
}
results.push(addHeaderID(line, slugger));
});
return results;
}
const [path] = process.argv.slice(2);
const files = walk(path);
files.forEach((file) => {
if (!(file.endsWith('.md') || file.endsWith('.mdx'))) {
return;
}
const content = fs.readFileSync(file, 'utf8');
const lines = content.split('\n');
const updatedLines = addHeaderIDs(lines);
fs.writeFileSync(file, updatedLines.join('\n'));
});

46
beta/scripts/generateRSS.js

@ -0,0 +1,46 @@
const RSS = require('rss');
const fs = require('fs-extra');
const authorsJson = require('../src/authors.json');
const blogIndexJson = require('../src/blogIndex.json');
const parse = require('date-fns/parse');
function removeFromLast(path, key) {
const i = path.lastIndexOf(key);
return i === -1 ? path : path.substring(0, i);
}
const SITE_URL = 'https://reactjs.org';
function generate() {
const feed = new RSS({
title: 'React.js Blog',
site_url: SITE_URL,
feed_url: SITE_URL + '/feed.xml',
});
blogIndexJson.routes.map((meta) => {
feed.item({
title: meta.title,
guid: removeFromLast(meta.path, '.'),
url: SITE_URL + removeFromLast(meta.path, '.'),
date: parse(meta.date, 'yyyy-MM-dd', new Date()),
description: meta.description,
custom_elements: [].concat(
meta.author.map((author) => ({
author: [{ name: authorsJson[author].name }],
}))
),
});
});
const rss = feed.xml({ indent: true });
fs.writeFileSync('./.next/static/feed.xml', rss);
}
try {
generate();
} catch (error) {
console.error('Error generating rss feed');
throw error;
}

50
beta/scripts/migrations/migrateBlogPosts.js

@ -0,0 +1,50 @@
const fs = require('fs-extra');
const path = require('path');
const fm = require('gray-matter');
const globby = require('globby');
const parse = require('date-fns/parse');
/**
* This script takes the gatsby blog posts directory and migrates it.
*
* In gatsby, blog posts were put in markdown files title YYYY-MM-DD-post-title.md.
* This script looks at that directory and then moves posts into folders paths
* that match the end URL structure of /blog/YYYY/MM/DD/postitle.md
*
* This allows us to use MDX in blog posts.
*/
// I dropped them into src/pages/oldblog
// @todo remove after migration
// I am not proud of this. Also, the blog posts needed to be cleaned up for MDX, don't run this again.
Promise.resolve()
.then(async () => {
const blogManifest = {};
const blogPosts = await globby('src/pages/oldblog/*.md');
// console.log(blogPosts);
for (let postpath of blogPosts.sort()) {
const rawStr = await fs.readFile(postpath, 'utf8');
// console.log(rawStr);
const {data, content} = fm(rawStr);
const cleanPath = postpath.replace('src/pages/oldblog/', '');
const yrStr = parseInt(cleanPath.substr(0, 4), 10); // 2013-06-02
// console.log(yrStr);
const dateStr = cleanPath.substr(0, 10); // 2013-06-02
const postFileName = cleanPath.substr(11);
// console.log(postFileName, dateStr);
const datePath = dateStr.split('-').join('/');
// console.log(datePath);
const newPath = './src/pages/blog/' + datePath + '/' + postFileName;
// console.log(newPath);
await fs.ensureFile(path.resolve(newPath));
await fs.writeFile(
path.resolve(newPath),
rawStr
.replace('<br>', '<br/>')
.replace('<hr>', '<hr/>')
.replace('layout: post', '')
.replace('\nauthor', '\nlayout: Post\nauthor')
);
}
})
.catch(console.error);

35
beta/scripts/migrations/migratePermalinks.js

@ -0,0 +1,35 @@
const fs = require('fs-extra');
const path = require('path');
const fm = require('gray-matter');
const globby = require('globby');
/**
* This script ensures that every file in the docs folder is named corresponding
* to its respective frontmatter permalink. In the old site, the path of the page was set by
* the `permalink` in markdown frontmatter, and not the name of the file itself or it's id.
* In the new Next.js site, with its filesystem router, the name of the file must
* match exactly to its `permalink`.
*/
Promise.resolve()
.then(async () => {
const pages = await globby('src/pages/docs/**/*.{md,mdx}');
for (let sourcePath of pages.sort()) {
const rawStr = await fs.readFile(sourcePath, 'utf8');
const {data, content} = fm(rawStr);
const currentPath = sourcePath
.replace('src/pages/', '')
.replace('.md', '');
const permalink = data.permalink.replace('.html', '');
if (permalink !== currentPath) {
const destPath = 'src/pages/' + permalink + '.md';
try {
await fs.move(sourcePath, destPath);
console.log(`MOVED: ${sourcePath} --> ${destPath}`);
} catch (error) {
console.error(`ERROR: ${sourcePath} --> ${destPath}`);
console.error(error);
}
}
}
})
.catch(console.error);

117
beta/scripts/migrations/migrateRedirects.js

@ -0,0 +1,117 @@
const fs = require('fs-extra');
const path = require('path');
const fm = require('gray-matter');
const globby = require('globby');
/**
* This script takes a look at all the redirect frontmatter and converts it
* into a Next.js compatible redirects list. It also merges it with netlify's
* _redirects, which we moved by hand below.
*
* @remarks
* In the old gatsby site, redirects were specified in docs and blog post
* frontmatter that looks like:
*
* ---
* redirect_from:
* - /docs/old-path.html#maybe-an-anchor
* ---
*/
const netlifyRedirects = [
{
source: '/html-jsx.html',
destination: 'https://magic.reactjs.net/htmltojsx.htm',
permanent: true,
},
{
source: '/tips/controlled-input-null-value.html',
destination: '/docs/forms.html#controlled-input-null-value',
permanent: false, // @todo why were these not permanent on netlify?
},
{
source: '/concurrent',
destination: '/docs/concurrent-mode-intro.html',
permanent: false,
},
{
source: '/hooks',
destination: '/docs/hooks-intro.html',
permanent: false,
},
{
source: '/tutorial',
destination: '/tutorial/tutorial.html',
permanent: false,
},
{
source: '/your-story',
destination: 'https://www.surveymonkey.co.uk/r/MVQV2R9',
permanent: true,
},
{
source: '/stories',
destination: 'https://medium.com/react-community-stories',
permanent: true,
},
];
Promise.resolve()
.then(async () => {
let contentRedirects = [];
let redirectPageCount = 0;
// Get all markdown pages
const pages = await globby('src/pages/**/*.{md,mdx}');
for (let filepath of pages) {
// Read file as string
const rawStr = await fs.readFile(filepath, 'utf8');
// Extract frontmatter
const {data, content} = fm(rawStr);
// Look for redirect yaml
if (data.redirect_from) {
redirectPageCount++;
let destinationPath = filepath
.replace('src/pages', '')
.replace('.md', '');
// Fix /docs/index -> /docs
if (destinationPath === '/docs/index') {
destinationPath = '/docs';
}
if (destinationPath === '/index') {
destinationPath = '/';
}
for (let sourcePath of data.redirect_from) {
contentRedirects.push({
source: '/' + sourcePath, // add slash
destination: destinationPath,
permanent: true,
});
}
}
}
console.log(
`Found ${redirectPageCount} pages with \`redirect_from\` frontmatter`
);
console.log(
`Writing ${contentRedirects.length} redirects to redirects.json`
);
await fs.writeFile(
path.resolve('./src/redirects.json'),
JSON.stringify(
{
redirects: [...contentRedirects, ...netlifyRedirects],
},
null,
2
)
);
console.log('✅ Done writing redirects');
})
.catch(console.error);

130
beta/src/authors.json

@ -0,0 +1,130 @@
{
"acdlite": {
"name": "Andrew Clark",
"url": "https://twitter.com/acdlite"
},
"benigeri": {
"name": "Paul Benigeri",
"url": "https://github.com/benigeri"
},
"bvaughn": {
"name": "Brian Vaughn",
"url": "https://github.com/bvaughn"
},
"chenglou": {
"name": "Cheng Lou",
"url": "https://twitter.com/_chenglou"
},
"clemmy": {
"name": "Clement Hoang",
"url": "https://twitter.com/c8hoang"
},
"Daniel15": {
"name": "Daniel Lo Nigro",
"url": "https://d.sb/"
},
"fisherwebdev": {
"name": "Bill Fisher",
"url": "https://twitter.com/fisherwebdev"
},
"flarnie": {
"name": "Flarnie Marchan",
"url": "https://twitter.com/ProbablyFlarnie"
},
"gaearon": {
"name": "Dan Abramov",
"url": "https://twitter.com/dan_abramov"
},
"jaredly": {
"name": "Jared Forsyth",
"url": "https://twitter.com/jaredforsyth"
},
"jgebhardt": {
"name": "Jonas Gebhardt",
"url": "https://twitter.com/jonasgebhardt"
},
"jimfb": {
"name": "Jim Sproch",
"url": "http: //www.jimsproch.com"
},
"jingc": {
"name": "Jing Chen",
"url": "https://twitter.com/jingc"
},
"josephsavona": {
"name": "Joseph Savona",
"url": "https://twitter.com/en_JS"
},
"keyanzhang": {
"name": "Keyan Zhang",
"url": "https://twitter.com/keyanzhang"
},
"kmeht": {
"name": "Kunal Mehta",
"url": "https://github.com/kmeht"
},
"LoukaN": {
"name": "Lou Husson",
"url": "https://twitter.com/loukan42"
},
"matthewjohnston4": {
"name": "Matthew Johnston",
"url": "https://github.com/matthewathome"
},
"nhunzaker": {
"name": "Nathan Hunzaker",
"url": "https://github.com/nhunzaker"
},
"petehunt": {
"name": "Pete Hunt",
"url": "https://twitter.com/floydophone"
},
"rachelnabors": {
"name": "Rachel Nabors",
"url": "https://twitter.com/rachelnabors"
},
"schrockn": {
"name": "Nick Schrock",
"url": "https://twitter.com/schrockn"
},
"sebmarkbage": {
"name": "Sebastian Markbåge",
"url": "https://twitter.com/sebmarkbage"
},
"sophiebits": {
"name": "Sophie Alpert",
"url": "https://sophiebits.com/"
},
"steveluscher": {
"name": "Steven Luscher",
"url": "https://twitter.com/steveluscher"
},
"tesseralis": {
"name": "Nat Alison",
"url": "https://twitter.com/tesseralis"
},
"threepointone": {
"name": "Sunil Pai",
"url": "https://twitter.com/threepointone"
},
"timer": {
"name": "Joe Haddad",
"url": "https://twitter.com/timer150"
},
"vjeux": {
"name": "Vjeux",
"url": "https://twitter.com/vjeux"
},
"wincent": {
"name": "Greg Hurrell",
"url": "https://twitter.com/wincent"
},
"zpao": {
"name": "Paul O’Shannessy",
"url": "https://twitter.com/zpao"
},
"tomocchino": {
"name": "Tom Occhino",
"url": "https://twitter.com/tomocchino"
}
}

1400
beta/src/blogIndex.json

File diff suppressed because it is too large

207
beta/src/blogIndexRecent.json

@ -0,0 +1,207 @@
{
"title": "Recent Posts",
"heading": true,
"path": "/blog",
"routes": [
{
"path": "/blog/2020/08/10/react-v17-rc.md",
"date": "2020-08-10",
"title": "React v17.0 Release Candidate: No New Features",
"author": ["gaearon", "rachelnabors"],
"excerpt": "<p>Today, we are publishing the first Release Candidate for React 17. It has been two and a half years since <a href=\"/blog/2017/09/26/react-v16.0.html\">the previous major release of React</a>, which is a long time even by our standards! In this blog post, we will describe the role of this major release, what changes you can expect in it, and how you can try this release.</p>\n",
"readingTime": "20 min read"
},
{
"path": "/blog/2020/02/26/react-v16.13.0.md",
"date": "2020-02-26",
"title": "React v16.13.0",
"author": ["threepointone"],
"excerpt": "<p>Today we are releasing React 16.13.0. It contains bugfixes and new deprecation warnings to help prepare for a future major release.</p>\n",
"readingTime": "7 min read"
},
{
"path": "/blog/2019/11/06/building-great-user-experiences-with-concurrent-mode-and-suspense.md",
"date": "2019-11-06",
"title": "Building Great User Experiences with Concurrent Mode and Suspense",
"author": ["josephsavona"],
"excerpt": "<p>At React Conf 2019 we announced an <a href=\"/docs/concurrent-mode-adoption#installation\">experimental release</a> of React that supports Concurrent Mode and Suspense. In this post we’ll introduce best practices for using them that we’ve identified through the process of building <a href=\"https://twitter.com/facebook/status/1123322299418124289\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">the new facebook.com</a>.</p>\n",
"readingTime": "17 min read"
},
{
"path": "/blog/2019/10/22/react-release-channels.md",
"date": "2019-10-22",
"title": "Preparing for the Future with React Prereleases",
"author": ["acdlite"],
"excerpt": "<p>To share upcoming changes with our partners in the React ecosystem, we’re establishing official prerelease channels. We hope this process will help us make changes to React with confidence, and give developers the opportunity to try out experimental features.</p>\n",
"readingTime": "7 min read"
},
{
"path": "/blog/2019/08/15/new-react-devtools.md",
"date": "2019-08-15",
"title": "Introducing the New React DevTools",
"author": ["bvaughn"],
"excerpt": "<p>We are excited to announce a new release of the React Developer Tools, available today in Chrome, Firefox, and (Chromium) Edge!</p>\n",
"readingTime": "2 min read"
},
{
"path": "/blog/2019/08/08/react-v16.9.0.md",
"date": "2019-08-08",
"title": "React v16.9.0 and the Roadmap Update",
"author": ["gaearon", "bvaughn"],
"excerpt": "<p>Today we are releasing React 16.9. It contains several new features, bugfixes, and new deprecation warnings to help prepare for a future major release.</p>\n",
"readingTime": "11 min read"
},
{
"path": "/blog/2019/02/23/is-react-translated-yet.md",
"date": "2019-02-23",
"title": "Is React Translated Yet? ¡Sí! Sim! はい!",
"author": ["tesseralis"],
"excerpt": "<p>We’re excited to announce an ongoing effort to maintain official translations of the React documentation website into different languages. Thanks to the dedicated efforts of React community members from around the world, React is now being translated into <em>over 30</em> languages! You can find them on the new <a href=\"/languages\">Languages</a> page.</p>\n",
"readingTime": "7 min read"
},
{
"path": "/blog/2019/02/06/react-v16.8.0.md",
"date": "2019-02-06",
"title": "React v16.8: The One With Hooks",
"author": ["gaearon"],
"excerpt": "<p>With React 16.8, <a href=\"/docs/hooks-intro.html\">React Hooks</a> are available in a stable release!</p>\n",
"readingTime": "7 min read"
},
{
"path": "/blog/2018/12/19/react-v-16-7.md",
"date": "2018-12-19",
"title": "React v16.7: No, This Is Not the One With Hooks",
"author": ["acdlite"],
"excerpt": "<p>Our latest release includes an important performance bugfix for <code>React.lazy</code>. Although there are no API changes, we’re releasing it as a minor instead of a patch.</p>\n",
"readingTime": "3 min read"
},
{
"path": "/blog/2018/11/27/react-16-roadmap.md",
"date": "2018-11-27",
"title": "React 16.x Roadmap",
"author": ["gaearon"],
"excerpt": "<p>You might have heard about features like “Hooks”, “Suspense”, and “Concurrent Rendering” in the previous blog posts and talks. In this post, we’ll look at how they fit together and the expected timeline for their availability in a stable release of React.</p>\n",
"readingTime": "14 min read"
},
{
"path": "/blog/2018/11/13/react-conf-recap.md",
"date": "2018-11-13",
"title": "React Conf recap: Hooks, Suspense, and Concurrent Rendering",
"author": ["tomocchino"],
"excerpt": "<p>This year’s <a href=\"https://conf.reactjs.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">React Conf</a> took place on October 25 and 26 in Henderson, Nevada, where more than 600 attendees gathered to discuss the latest in UI engineering.</p>\n",
"readingTime": "2 min read"
},
{
"path": "/blog/2018/10/23/react-v-16-6.md",
"date": "2018-10-23",
"title": "React v16.6.0: lazy, memo and contextType",
"author": ["sebmarkbage"],
"excerpt": "<p>Today we’re releasing React 16.6 with a few new convenient features. A form of PureComponent/shouldComponentUpdate for function components, a way to do code splitting using Suspense and an easier way to consume Context from class components.</p>\n",
"readingTime": "5 min read"
},
{
"path": "/blog/2018/10/01/create-react-app-v2.md",
"date": "2018-10-01",
"title": "Create React App 2.0: Babel 7, Sass, and More",
"author": ["timer", "gaearon"],
"excerpt": "<p>Create React App 2.0 has been released today, and it brings a year’s worth of improvements in a single dependency update.</p>\n",
"readingTime": "6 min read"
},
{
"path": "/blog/2018/09/10/introducing-the-react-profiler.md",
"date": "2018-09-10",
"title": "Introducing the React Profiler",
"author": ["bvaughn"],
"excerpt": "<p>React 16.5 adds support for a new DevTools profiler plugin.</p>\n",
"readingTime": "8 min read"
},
{
"path": "/blog/2018/08/01/react-v-16-4-2.md",
"date": "2018-08-01",
"title": "React v16.4.2: Server-side vulnerability fix",
"author": ["gaearon"],
"excerpt": "<p>We discovered a minor vulnerability that might affect some apps using ReactDOMServer. We are releasing a patch version for every affected React minor release so that you can upgrade with no friction. Read on for more details.</p>\n",
"readingTime": "4 min read"
},
{
"path": "/blog/2018/06/07/you-probably-dont-need-derived-state.md",
"date": "2018-06-07",
"title": "You Probably Don't Need Derived State",
"author": ["bvaughn"],
"excerpt": "<p>React 16.4 included a <a href=\"/blog/2018/05/23/react-v-16-4#bugfix-for-getderivedstatefromprops\">bugfix for getDerivedStateFromProps</a> which caused some existing bugs in React components to reproduce more consistently. If this release exposed a case where your application was using an anti-pattern and didn’t work properly after the fix, we’re sorry for the churn. In this post, we will explain some common anti-patterns with derived state and our preferred alternatives.</p>\n",
"readingTime": "14 min read"
},
{
"path": "/blog/2018/05/23/react-v-16-4.md",
"date": "2018-05-23",
"title": "React v16.4.0: Pointer Events",
"author": ["acdlite"],
"excerpt": "<p>The latest minor release adds support for an oft-requested feature: pointer events!</p>\n",
"readingTime": "5 min read"
},
{
"path": "/blog/2018/03/29/react-v-16-3.md",
"date": "2018-03-29",
"title": "React v16.3.0: New lifecycles and context API",
"author": ["bvaughn"],
"excerpt": "<p>A few days ago, we <a href=\"/blog/2018/03/27/update-on-async-rendering\">wrote a post about upcoming changes to our legacy lifecycle methods</a>, including gradual migration strategies. In React 16.3.0, we are adding a few new lifecycle methods to assist with that migration. We are also introducing new APIs for long requested features: an official context API, a ref forwarding API, and an ergonomic ref API.</p>\n",
"readingTime": "6 min read"
},
{
"path": "/blog/2018/03/27/update-on-async-rendering.md",
"date": "2018-03-27",
"title": "Update on Async Rendering",
"author": ["bvaughn"],
"excerpt": "<p>For over a year, the React team has been working to implement asynchronous rendering. Last month during his talk at JSConf Iceland, <a href=\"/blog/2018/03/01/sneak-peek-beyond-react-16\">Dan unveiled some of the exciting new possibilities async rendering unlocks</a>. Now we’d like to share with you some of the lessons we’ve learned while working on these features, and some recipes to help prepare your components for async rendering when it launches.</p>\n",
"readingTime": "12 min read"
},
{
"path": "/blog/2018/03/01/sneak-peek-beyond-react-16.md",
"date": "2018-03-01",
"title": "Sneak Peek: Beyond React 16",
"author": ["sophiebits"],
"excerpt": "<p><a href=\"https://twitter.com/dan_abramov\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Dan Abramov</a> from our team just spoke at <a href=\"https://2018.jsconf.is/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">JSConf Iceland 2018</a> with a preview of some new features we’ve been working on in React. The talk opens with a question: “With vast differences in computing power and network speed, how do we deliver the best user experience for everyone?”</p>\n",
"readingTime": "2 min read"
},
{
"path": "/blog/2017/12/15/improving-the-repository-infrastructure.md",
"date": "2017-12-15",
"title": "Behind the Scenes: Improving the Repository Infrastructure",
"author": ["gaearon", "bvaughn"],
"excerpt": "<p>As we worked on <a href=\"/blog/2017/09/26/react-v16.0\">React 16</a>, we revamped the folder structure and much of the build tooling in the React repository. Among other things, we introduced projects such as <a href=\"https://rollupjs.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Rollup</a>, <a href=\"https://prettier.io/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Prettier</a>, and <a href=\"https://developers.google.com/closure/compiler/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Google Closure Compiler</a> into our workflow. People often ask us questions about how we use those tools. In this post, we would like to share some of the changes that we’ve made to our build and test infrastructure in 2017, and what motivated them.</p>\n",
"readingTime": "30 min read"
},
{
"path": "/blog/2017/12/07/introducing-the-react-rfc-process.md",
"date": "2017-12-07",
"title": "Introducing the React RFC Process",
"author": ["acdlite"],
"excerpt": "<p>We’re adopting an RFC (“request for comments”) process for contributing ideas to React.</p>\n",
"readingTime": "2 min read"
},
{
"path": "/blog/2017/11/28/react-v16.2.0-fragment-support.md",
"date": "2017-11-28",
"title": "React v16.2.0: Improved Support for Fragments",
"author": ["clemmy"],
"excerpt": "<p>React 16.2 is now available! The biggest addition is improved support for returning multiple children from a component’s render method. We call this feature <em>fragments</em>:</p>\n",
"readingTime": "8 min read"
},
{
"path": "/blog/2017/09/26/react-v16.0.md",
"date": "2017-09-26",
"title": "React v16.0",
"author": ["acdlite"],
"excerpt": "<p>We’re excited to announce the release of React v16.0! Among the changes are some long-standing feature requests, including <a href=\"#new-render-return-types-fragments-and-strings\"><strong>fragments</strong></a>, <a href=\"#better-error-handling\"><strong>error boundaries</strong></a>, <a href=\"#portals\"><strong>portals</strong></a>, support for <a href=\"#support-for-custom-dom-attributes\"><strong>custom DOM attributes</strong></a>, improved <a href=\"#better-server-side-rendering\"><strong>server-side rendering</strong></a>, and <a href=\"#reduced-file-size\"><strong>reduced file size</strong></a>.</p>\n",
"readingTime": "11 min read"
},
{
"path": "/blog/2017/09/25/react-v15.6.2.md",
"date": "2017-09-25",
"title": "React v15.6.2",
"author": ["nhunzaker"],
"excerpt": "<p>Today we’re sending out React 15.6.2. In 15.6.1, we shipped a few fixes for change events and inputs that had some unintended consequences. Those regressions have been ironed out, and we’ve also included a few more fixes to improve the stability of React across all browsers.</p>\n",
"readingTime": "3 min read"
}
]
}

45
beta/src/components/Breadcrumbs.tsx

@ -0,0 +1,45 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {useRouteMeta} from 'components/Layout/useRouteMeta';
import Link from 'next/link';
function Breadcrumbs() {
const {breadcrumbs} = useRouteMeta();
if (!breadcrumbs) return null;
return (
<div className="flex">
{breadcrumbs.map(
(crumb, i) =>
crumb.path && (
<div className="flex mb-3 mt-0.5 items-center" key={i}>
<React.Fragment key={crumb.path}>
<Link href={crumb.path}>
<a className="text-link dark:text-link-dark text-sm tracking-wide font-bold uppercase mr-1 hover:underline">
{crumb.title}
</a>
</Link>
<span className="inline-block mr-1 text-link dark:text-link-dark text-lg">
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M6.86612 13.6161C6.37796 14.1043 6.37796 14.8957 6.86612 15.3839C7.35427 15.872 8.14572 15.872 8.63388 15.3839L13.1339 10.8839C13.622 10.3957 13.622 9.60428 13.1339 9.11612L8.63388 4.61612C8.14572 4.12797 7.35427 4.12797 6.86612 4.61612C6.37796 5.10428 6.37796 5.89573 6.86612 6.38388L10.4822 10L6.86612 13.6161Z"
fill="currentColor"
/>
</svg>
</span>
</React.Fragment>
</div>
)
)}
</div>
);
}
export default Breadcrumbs;

49
beta/src/components/Button.tsx

@ -0,0 +1,49 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
interface ButtonProps {
children: React.ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
active: boolean;
className?: string;
style?: Record<string, string>;
}
export function Button({
children,
onClick,
active,
className,
style,
}: ButtonProps) {
return (
<button
style={style}
onMouseDown={(evt) => {
evt.preventDefault();
evt.stopPropagation();
}}
onClick={onClick}
className={cn(
className,
'text-base leading-tight font-bold border rounded-lg py-2 px-4 focus:ring-1 focus:ring-offset-2 focus:ring-link active:bg-link active:border-link active:text-white active:ring-0 active:ring-offset-0 outline-none inline-flex items-center my-1',
{
'bg-link border-link text-white hover:bg-link focus:bg-link active:bg-link': active,
'bg-transparent text-secondary dark:text-secondary-dark bg-secondary-button dark:bg-secondary-button-dark hover:text-link focus:text-link border-transparent': !active,
}
)}>
{children}
</button>
);
}
Button.defaultProps = {
active: false,
style: {},
};
export default Button;

46
beta/src/components/ButtonLink.tsx

@ -0,0 +1,46 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import NextLink from 'next/link';
interface ButtonLinkProps {
size?: 'md' | 'lg';
type?: 'primary' | 'secondary';
label?: string;
target?: '_self' | '_blank';
}
function ButtonLink({
href,
className,
children,
type = 'primary',
size = 'md',
label,
target = '_self',
...props
}: JSX.IntrinsicElements['a'] & ButtonLinkProps) {
const classes = cn(
className,
'inline-flex font-bold items-center border-2 border-transparent outline-none focus:ring-1 focus:ring-offset-2 focus:ring-link active:bg-link active:text-white active:ring-0 active:ring-offset-0 leading-normal',
{
'bg-link text-white hover:bg-opacity-80': type === 'primary',
'bg-secondary-button dark:bg-secondary-button-dark text-primary dark:text-primary-dark hover:text-link focus:bg-link focus:text-white focus:border-link focus:border-2':
type === 'secondary',
'text-lg rounded-lg p-4': size === 'lg',
'text-base rounded-lg px-4 py-1.5': size === 'md',
}
);
return (
<NextLink href={href as string}>
<a className={classes} {...props} aria-label={label} target={target}>
{children}
</a>
</NextLink>
);
}
export default ButtonLink;

93
beta/src/components/DocsFooter.tsx

@ -0,0 +1,93 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import NextLink from 'next/link';
import * as React from 'react';
import cn from 'classnames';
import {removeFromLast} from 'utils/removeFromLast';
import {IconNavArrow} from './Icon/IconNavArrow';
import {RouteMeta} from './Layout/useRouteMeta';
export type DocsPageFooterProps = Pick<
RouteMeta,
'route' | 'nextRoute' | 'prevRoute'
>;
function areEqual(prevProps: DocsPageFooterProps, props: DocsPageFooterProps) {
return prevProps.route?.path === props.route?.path;
}
export const DocsPageFooter = React.memo<DocsPageFooterProps>(
function DocsPageFooter({nextRoute, prevRoute, route}) {
if (!route || route?.heading) {
return null;
}
return (
<>
{prevRoute?.path || nextRoute?.path ? (
<>
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-2 gap-4 py-4 md:py-12">
{prevRoute?.path ? (
<FooterLink
type="Previous"
title={prevRoute.title}
href={removeFromLast(prevRoute.path, '.')}
/>
) : (
<div />
)}
{nextRoute?.path ? (
<FooterLink
type="Next"
title={nextRoute.title}
href={removeFromLast(nextRoute.path, '.')}
/>
) : (
<div />
)}
</div>
</>
) : null}
</>
);
},
areEqual
);
function FooterLink({
href,
title,
type,
}: {
href: string;
title: string;
type: 'Previous' | 'Next';
}) {
return (
<NextLink href={href}>
<a
className={cn(
'flex gap-x-4 md:gap-x-6 items-center w-full md:w-80 px-4 md:px-5 py-6 border-2 border-transparent text-base leading-base text-link dark:text-link-dark rounded-lg group focus:text-link dark:focus:text-link-dark focus:bg-highlight focus:border-link dark:focus:bg-highlight-dark dark:focus:border-link-dark focus:border-opacity-100 focus:border-2 focus:ring-1 focus:ring-offset-4 focus:ring-blue-40 active:ring-0 active:ring-offset-0 hover:bg-gray-5 dark:hover:bg-gray-80',
{
'flex-row-reverse justify-self-end text-right': type === 'Next',
}
)}>
<IconNavArrow
className="text-gray-30 dark:text-gray-50 inline group-focus:text-link dark:group-focus:text-link-dark"
displayDirection={type === 'Previous' ? 'left' : 'right'}
/>
<span>
<span className="block no-underline text-sm tracking-wide text-secondary dark:text-secondary-dark uppercase font-bold group-focus:text-link dark:group-focus:text-link-dark group-focus:text-opacity-100">
{type}
</span>
<span className="block text-lg group-hover:underline">{title}</span>
</span>
</a>
</NextLink>
);
}
DocsPageFooter.displayName = 'DocsPageFooter';

20
beta/src/components/ExternalLink.tsx

@ -0,0 +1,20 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export function ExternalLink({
href,
target,
children,
...props
}: JSX.IntrinsicElements['a']) {
return (
<a href={href} target={target ?? '_blank'} rel="noopener" {...props}>
{children}
</a>
);
}
ExternalLink.displayName = 'ExternalLink';

30
beta/src/components/Icon/IconArrow.tsx

@ -0,0 +1,30 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
export const IconArrow = React.memo<
JSX.IntrinsicElements['svg'] & {
displayDirection: 'left' | 'right' | 'up' | 'down';
}
>(function IconArrow({displayDirection, className, ...rest}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1.33em"
height="1.33em"
fill="currentColor"
{...rest}
className={cn(className, {
'transform rotate-180': displayDirection === 'right',
})}>
<path fill="none" d="M0 0h24v24H0z" />
<path d="M7.828 11H20v2H7.828l5.364 5.364-1.414 1.414L4 12l7.778-7.778 1.414 1.414z" />
</svg>
);
});
IconArrow.displayName = 'IconArrow';

33
beta/src/components/Icon/IconArrowSmall.tsx

@ -0,0 +1,33 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
export const IconArrowSmall = React.memo<
JSX.IntrinsicElements['svg'] & {
displayDirection: 'left' | 'right' | 'up' | 'down';
}
>(function IconArrowSmall({displayDirection, className, ...rest}) {
const classes = cn(className, {
'transform rotate-180': displayDirection === 'left',
'transform rotate-90': displayDirection === 'down',
});
return (
<svg
width="1em"
height="1em"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={classes}
{...rest}>
<path
d="M6.86612 13.6161C6.37796 14.1043 6.37796 14.8957 6.86612 15.3839C7.35427 15.872 8.14572 15.872 8.63388 15.3839L13.1339 10.8839C13.622 10.3957 13.622 9.60428 13.1339 9.11612L8.63388 4.61612C8.14572 4.12797 7.35427 4.12797 6.86612 4.61612C6.37796 5.10428 6.37796 5.89573 6.86612 6.38388L10.4822 10L6.86612 13.6161Z"
fill="currentColor"></path>
</svg>
);
});
IconArrowSmall.displayName = 'IconArrowSmall';

42
beta/src/components/Icon/IconChevron.tsx

@ -0,0 +1,42 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
export const IconChevron = React.memo<
JSX.IntrinsicElements['svg'] & {
displayDirection: 'up' | 'down' | 'left' | 'right';
}
>(function IconChevron({className, displayDirection, ...rest}) {
const classes = cn(
{
'transform rotate-0': displayDirection === 'down',
'transform rotate-90': displayDirection === 'left',
'transform rotate-180': displayDirection === 'up',
'transform -rotate-90': displayDirection === 'right',
},
className
);
return (
<svg
className={classes}
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20">
<g fill="none" fillRule="evenodd" transform="translate(-446 -398)">
<path
fill="currentColor"
fillRule="nonzero"
d="M95.8838835,240.366117 C95.3957281,239.877961 94.6042719,239.877961 94.1161165,240.366117 C93.6279612,240.854272 93.6279612,241.645728 94.1161165,242.133883 L98.6161165,246.633883 C99.1042719,247.122039 99.8957281,247.122039 100.383883,246.633883 L104.883883,242.133883 C105.372039,241.645728 105.372039,240.854272 104.883883,240.366117 C104.395728,239.877961 103.604272,239.877961 103.116117,240.366117 L99.5,243.982233 L95.8838835,240.366117 Z"
transform="translate(356.5 164.5)"
/>
<polygon points="446 418 466 418 466 398 446 398" />
</g>
</svg>
);
});
IconChevron.displayName = 'IconChevron';

28
beta/src/components/Icon/IconClose.tsx

@ -0,0 +1,28 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconClose = React.memo<JSX.IntrinsicElements['svg']>(
function IconClose(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.33em"
height="1.33em"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}>
<line x1={18} y1={6} x2={6} y2={18} />
<line x1={6} y1={6} x2={18} y2={18} />
</svg>
);
}
);
IconClose.displayName = 'IconClose';

26
beta/src/components/Icon/IconCodeBlock.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconCodeBlock = React.memo<JSX.IntrinsicElements['svg']>(
function IconCodeBlock({className}) {
return (
<svg
className={className}
width="1.33em"
height="1em"
viewBox="0 0 24 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M24 9L18.343 14.657L16.929 13.243L21.172 9L16.929 4.757L18.343 3.343L24 9ZM2.828 9L7.071 13.243L5.657 14.657L0 9L5.657 3.343L7.07 4.757L2.828 9ZM9.788 18H7.66L14.212 0H16.34L9.788 18Z"
fill="currentColor"
/>
</svg>
);
}
);
IconCodeBlock.displayName = 'IconCodeBlock';

26
beta/src/components/Icon/IconDeepDive.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconDeepDive = React.memo<JSX.IntrinsicElements['svg']>(
function IconDeepDive({className}) {
return (
<svg
className={className}
width="0.78em"
height="0.78em"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M7.62728 12.3491V13.6825H6.29395V12.3491H0.960612C0.783801 12.3491 0.614232 12.2789 0.489207 12.1539C0.364183 12.0288 0.293945 11.8593 0.293945 11.6825V1.01579C0.293945 0.838979 0.364183 0.669409 0.489207 0.544385C0.614232 0.419361 0.783801 0.349123 0.960612 0.349123H4.96061C5.339 0.348674 5.71313 0.42896 6.05803 0.584621C6.40292 0.740282 6.71063 0.967734 6.96061 1.25179C7.2106 0.967734 7.51831 0.740282 7.8632 0.584621C8.20809 0.42896 8.58222 0.348674 8.96061 0.349123H12.9606C13.1374 0.349123 13.307 0.419361 13.432 0.544385C13.557 0.669409 13.6273 0.838979 13.6273 1.01579V11.6825C13.6273 11.8593 13.557 12.0288 13.432 12.1539C13.307 12.2789 13.1374 12.3491 12.9606 12.3491H7.62728ZM12.2939 11.0158V1.68246H8.96061C8.60699 1.68246 8.26785 1.82293 8.0178 2.07298C7.76776 2.32303 7.62728 2.66217 7.62728 3.01579V11.0158H12.2939ZM6.29395 11.0158V3.01579C6.29395 2.66217 6.15347 2.32303 5.90342 2.07298C5.65337 1.82293 5.31423 1.68246 4.96061 1.68246H1.62728V11.0158H6.29395Z"
fill="currentColor"
/>
</svg>
);
}
);
IconDeepDive.displayName = 'IconDeepDive';

24
beta/src/components/Icon/IconError.tsx

@ -0,0 +1,24 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconError = React.memo<JSX.IntrinsicElements['svg']>(
function IconError({className}) {
return (
<svg
className={className}
width="1.33em"
height="1.33em"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<circle cx="10.1626" cy="9.99951" r="9.47021" fill="currentColor" />
<path d="M6.22705 5.95996L14.2798 14.0127" stroke="white" />
<path d="M14.2798 5.95996L6.22705 14.0127" stroke="white" />
</svg>
);
}
);
IconError.displayName = 'IconError';

22
beta/src/components/Icon/IconFacebookCircle.tsx

@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconFacebookCircle = React.memo<JSX.IntrinsicElements['svg']>(
function IconFacebookCircle(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1.33em"
height="1.33em"
fill="currentColor"
{...props}>
<path fill="none" d="M0 0h24v24H0z" />
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.879V14.89h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.989C18.343 21.129 22 16.99 22 12c0-5.523-4.477-10-10-10z" />
</svg>
);
}
);

22
beta/src/components/Icon/IconGitHub.tsx

@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconGitHub = React.memo<JSX.IntrinsicElements['svg']>(
function IconGitHub(props) {
return (
<svg
width="1em"
fill="currentColor"
height="1em"
viewBox="0 0 20 20"
{...props}>
<path d="M10 0a10 10 0 0 0-3.16 19.49c.5.1.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.46-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.52 2.34 1.08 2.91.83.1-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.1.39-1.99 1.03-2.69a3.6 3.6 0 0 1 .1-2.64s.84-.27 2.75 1.02a9.58 9.58 0 0 1 5 0c1.91-1.3 2.75-1.02 2.75-1.02.55 1.37.2 2.4.1 2.64.64.7 1.03 1.6 1.03 2.69 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85l-.01 2.75c0 .26.18.58.69.48A10 10 0 0 0 10 0"></path>
</svg>
);
}
);
IconGitHub.displayName = 'IconGitHub';

26
beta/src/components/Icon/IconGotcha.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconGotcha = React.memo<JSX.IntrinsicElements['svg']>(
function IconGotcha({className}) {
return (
<svg
className={className}
width="1.11em"
height="1.11em"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M19 0.398926C19.552 0.398926 20 0.846926 20 1.39893V15.3989C20 15.9509 19.552 16.3989 19 16.3989H4.455L0 19.8989V1.39893C0 0.846926 0.448 0.398926 1 0.398926H19ZM18 2.39893H2V15.7839L3.763 14.3989H18V2.39893ZM8.515 4.81093L8.962 5.49893C7.294 6.40193 7.323 7.85093 7.323 8.16293C7.478 8.14293 7.641 8.13893 7.803 8.15393C8.705 8.23793 9.416 8.97893 9.416 9.89893C9.416 10.8649 8.632 11.6489 7.666 11.6489C7.129 11.6489 6.616 11.4039 6.292 11.0589C5.777 10.5129 5.5 9.89893 5.5 8.90393C5.5 7.15393 6.728 5.58593 8.515 4.81093ZM13.515 4.81093L13.962 5.49893C12.294 6.40193 12.323 7.85093 12.323 8.16293C12.478 8.14293 12.641 8.13893 12.803 8.15393C13.705 8.23793 14.416 8.97893 14.416 9.89893C14.416 10.8649 13.632 11.6489 12.666 11.6489C12.129 11.6489 11.616 11.4039 11.292 11.0589C10.777 10.5129 10.5 9.89893 10.5 8.90393C10.5 7.15393 11.728 5.58593 13.515 4.81093Z"
fill="currentColor"
/>
</svg>
);
}
);
IconGotcha.displayName = 'IconGotcha';

28
beta/src/components/Icon/IconHamburger.tsx

@ -0,0 +1,28 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconHamburger = React.memo<JSX.IntrinsicElements['svg']>(
function IconHamburger(props) {
return (
<svg
width="1.33em"
height="1.33em"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
);
}
);
IconHamburger.displayName = 'IconHamburger';

26
beta/src/components/Icon/IconHint.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
export const IconHint = React.memo<JSX.IntrinsicElements['svg']>(
function IconHint({className}) {
return (
<svg
className={cn('inline -mt-0.5', className)}
width="12"
height="14"
viewBox="0 0 12 15"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4.53487 11H5.21954V7.66665H6.55287V11H7.23754C7.32554 10.1986 7.7342 9.53732 8.39754 8.81532C8.47287 8.73398 8.9522 8.23732 9.00887 8.16665C9.47973 7.5784 9.77486 6.86913 9.86028 6.1205C9.9457 5.37187 9.81794 4.61434 9.4917 3.93514C9.16547 3.25594 8.65402 2.6827 8.01628 2.28143C7.37853 1.88016 6.64041 1.66719 5.88692 1.66703C5.13344 1.66686 4.39523 1.87953 3.75731 2.28052C3.11939 2.68152 2.60771 3.25454 2.28118 3.9336C1.95465 4.61266 1.82656 5.37014 1.91167 6.1188C1.99677 6.86747 2.2916 7.57687 2.7622 8.16532C2.81954 8.23665 3.3002 8.73398 3.3742 8.81465C4.0382 9.53732 4.44687 10.1986 4.53487 11ZM4.55287 12.3333V13H7.21954V12.3333H4.55287ZM1.7222 8.99998C1.09433 8.21551 0.700836 7.26963 0.587047 6.2713C0.473258 5.27296 0.643804 4.26279 1.07904 3.35715C1.51428 2.4515 2.19649 1.68723 3.04711 1.15237C3.89772 0.617512 4.88213 0.333824 5.88692 0.333984C6.89172 0.334145 7.87604 0.61815 8.72648 1.15328C9.57692 1.68841 10.2589 2.4529 10.6938 3.35869C11.1288 4.26447 11.299 5.27469 11.1849 6.27299C11.0708 7.27129 10.677 8.21705 10.0489 9.00132C9.63554 9.51598 8.55287 10.3333 8.55287 11.3333V13C8.55287 13.3536 8.41239 13.6927 8.16235 13.9428C7.9123 14.1928 7.57316 14.3333 7.21954 14.3333H4.55287C4.19925 14.3333 3.86011 14.1928 3.61006 13.9428C3.36001 13.6927 3.21954 13.3536 3.21954 13V11.3333C3.21954 10.3333 2.1362 9.51598 1.7222 8.99998Z"
fill="currentColor"
/>
</svg>
);
}
);
IconHint.displayName = 'IconHint';

22
beta/src/components/Icon/IconInstagram.tsx

@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconInstagram = React.memo<JSX.IntrinsicElements['svg']>(
function IconInstagram(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1.33em"
height="1.33em"
fill="currentColor"
{...props}>
<path fill="none" d="M0 0h24v24H0z" />
<path d="M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm0-2a5 5 0 1 1 0 10 5 5 0 0 1 0-10zm6.5-.25a1.25 1.25 0 0 1-2.5 0 1.25 1.25 0 0 1 2.5 0zM12 4c-2.474 0-2.878.007-4.029.058-.784.037-1.31.142-1.798.332-.434.168-.747.369-1.08.703a2.89 2.89 0 0 0-.704 1.08c-.19.49-.295 1.015-.331 1.798C4.006 9.075 4 9.461 4 12c0 2.474.007 2.878.058 4.029.037.783.142 1.31.331 1.797.17.435.37.748.702 1.08.337.336.65.537 1.08.703.494.191 1.02.297 1.8.333C9.075 19.994 9.461 20 12 20c2.474 0 2.878-.007 4.029-.058.782-.037 1.309-.142 1.797-.331.433-.169.748-.37 1.08-.702.337-.337.538-.65.704-1.08.19-.493.296-1.02.332-1.8.052-1.104.058-1.49.058-4.029 0-2.474-.007-2.878-.058-4.029-.037-.782-.142-1.31-.332-1.798a2.911 2.911 0 0 0-.703-1.08 2.884 2.884 0 0 0-1.08-.704c-.49-.19-1.016-.295-1.798-.331C14.925 4.006 14.539 4 12 4zm0-2c2.717 0 3.056.01 4.122.06 1.065.05 1.79.217 2.428.465.66.254 1.216.598 1.772 1.153a4.908 4.908 0 0 1 1.153 1.772c.247.637.415 1.363.465 2.428.047 1.066.06 1.405.06 4.122 0 2.717-.01 3.056-.06 4.122-.05 1.065-.218 1.79-.465 2.428a4.883 4.883 0 0 1-1.153 1.772 4.915 4.915 0 0 1-1.772 1.153c-.637.247-1.363.415-2.428.465-1.066.047-1.405.06-4.122.06-2.717 0-3.056-.01-4.122-.06-1.065-.05-1.79-.218-2.428-.465a4.89 4.89 0 0 1-1.772-1.153 4.904 4.904 0 0 1-1.153-1.772c-.248-.637-.415-1.363-.465-2.428C2.013 15.056 2 14.717 2 12c0-2.717.01-3.056.06-4.122.05-1.066.217-1.79.465-2.428a4.88 4.88 0 0 1 1.153-1.772A4.897 4.897 0 0 1 5.45 2.525c.638-.248 1.362-.415 2.428-.465C8.944 2.013 9.283 2 12 2z" />
</svg>
);
}
);

44
beta/src/components/Icon/IconNavArrow.tsx

@ -0,0 +1,44 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
export const IconNavArrow = React.memo<
JSX.IntrinsicElements['svg'] & {
displayDirection: 'right' | 'down' | 'left';
}
>(function IconNavArrow({displayDirection = 'right', className, ...rest}) {
const classes = cn(
'duration-100 ease-in transition',
{
'transform rotate-0': displayDirection === 'down',
'transform -rotate-90': displayDirection === 'right',
'transform rotate-90': displayDirection === 'left',
},
className
);
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
className={classes}
style={{minWidth: 20, minHeight: 20}}>
<g fill="none" fillRule="evenodd" transform="translate(-446 -398)">
<path
fill="currentColor"
fillRule="nonzero"
d="M95.8838835,240.366117 C95.3957281,239.877961 94.6042719,239.877961 94.1161165,240.366117 C93.6279612,240.854272 93.6279612,241.645728 94.1161165,242.133883 L98.6161165,246.633883 C99.1042719,247.122039 99.8957281,247.122039 100.383883,246.633883 L104.883883,242.133883 C105.372039,241.645728 105.372039,240.854272 104.883883,240.366117 C104.395728,239.877961 103.604272,239.877961 103.116117,240.366117 L99.5,243.982233 L95.8838835,240.366117 Z"
transform="translate(356.5 164.5)"
/>
<polygon points="446 418 466 418 466 398 446 398" />
</g>
</svg>
);
});
IconNavArrow.displayName = 'IconNavArrow';

25
beta/src/components/Icon/IconNewPage.tsx

@ -0,0 +1,25 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconNewPage = React.memo<JSX.IntrinsicElements['svg']>(
function IconNewPage({className}) {
return (
<svg
className={className}
width="0.72em"
height="0.72em"
viewBox="0 0 13 13"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4.72038 2.94824V4.28158H1.38704V11.6149H8.72038V8.28158H10.0537V12.2816C10.0537 12.4584 9.98347 12.628 9.85845 12.753C9.73343 12.878 9.56386 12.9482 9.38704 12.9482H0.720378C0.543567 12.9482 0.373997 12.878 0.248973 12.753C0.123949 12.628 0.0537109 12.4584 0.0537109 12.2816V3.61491C0.0537109 3.4381 0.123949 3.26853 0.248973 3.1435C0.373997 3.01848 0.543567 2.94824 0.720378 2.94824H4.72038ZM12.0537 0.948242V6.28158H10.7204V3.22358L5.52504 8.41958L4.58238 7.47691L9.77704 2.28158H6.72038V0.948242H12.0537Z"
fill="currentColor"
/>
</svg>
);
}
);
IconNewPage.displayName = 'IconNewPage';

26
beta/src/components/Icon/IconNote.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconNote = React.memo<JSX.IntrinsicElements['svg']>(
function IconNote({className}) {
return (
<svg
className={className}
width="1em"
height="1.05em"
viewBox="0 0 18 19"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M18 12.2632L12 18.2592L1.002 18.2632C0.737486 18.2642 0.483369 18.1603 0.295486 17.9741C0.107603 17.7879 0.00132309 17.5347 0 17.2702V1.25618C0 0.708184 0.445 0.263184 0.993 0.263184H17.007C17.555 0.263184 18 0.719183 18 1.26518V12.2632ZM16 2.26318H2V16.2632H10V11.2632C10 11.0183 10.09 10.7818 10.2527 10.5988C10.4155 10.4158 10.6397 10.2988 10.883 10.2702L11 10.2632L16 10.2622V2.26318ZM15.171 12.2622L12 12.2632V15.4322L15.171 12.2622Z"
fill="currentColor"
/>
</svg>
);
}
);
IconNote.displayName = 'IconNote';

25
beta/src/components/Icon/IconRestart.tsx

@ -0,0 +1,25 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconRestart = React.memo<JSX.IntrinsicElements['svg']>(
function IconRestart({className}) {
return (
<svg
className={className}
width="0.78em"
height="0.78em"
viewBox="0 0 14 14"
xmlns="http://www.w3.org/2000/svg">
<path
d="M11.3567 11.9929C10.147 13.0412 8.59937 13.6172 6.9987 13.615C3.3167 13.615 0.332031 10.6303 0.332031 6.94828C0.332031 3.26628 3.3167 0.281616 6.9987 0.281616C10.6807 0.281616 13.6654 3.26628 13.6654 6.94828C13.6654 8.37228 13.2187 9.69228 12.4587 10.775L10.332 6.94828H12.332C12.3319 5.71909 11.9072 4.52766 11.1298 3.57554C10.3524 2.62343 9.26994 1.96908 8.06559 1.72319C6.86124 1.4773 5.60892 1.65496 4.52048 2.22613C3.43205 2.79729 2.57431 3.72689 2.09238 4.85767C1.61045 5.98845 1.5339 7.25099 1.87569 8.43171C2.21748 9.61243 2.95663 10.6388 3.96809 11.3373C4.97955 12.0358 6.20123 12.3635 7.42646 12.2649C8.6517 12.1663 9.80527 11.6475 10.692 10.7963L11.3567 11.9929Z"
fill="currentColor"
/>
</svg>
);
}
);
IconRestart.displayName = 'IconRestart';

29
beta/src/components/Icon/IconRss.tsx

@ -0,0 +1,29 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconRss = React.memo<JSX.IntrinsicElements['svg']>(
function IconRss(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}>
<path d="M4 11a9 9 0 0 1 9 9" />
<path d="M4 4a16 16 0 0 1 16 16" />
<circle cx={5} cy={19} r={1} />
</svg>
);
}
);
IconRss.displayName = 'IconLogo';

24
beta/src/components/Icon/IconSearch.tsx

@ -0,0 +1,24 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconSearch = React.memo<JSX.IntrinsicElements['svg']>(
function IconSearch(props) {
return (
<svg width="1em" height="1em" viewBox="0 0 20 20" {...props}>
<path
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
stroke="currentColor"
fill="none"
strokeWidth="2"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"></path>
</svg>
);
}
);
IconSearch.displayName = 'IconSearch';

26
beta/src/components/Icon/IconSolution.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
export const IconSolution = React.memo<JSX.IntrinsicElements['svg']>(
function IconSolution({className}) {
return (
<svg
className={cn('inline', className)}
width="0.75em"
height="0.75em"
viewBox="0 0 13 13"
xmlns="http://www.w3.org/2000/svg">
<path
d="M2.21908 8.74479V12.7448H0.885742V0.078125H7.14041C7.26418 0.0781911 7.3855 0.112714 7.49076 0.177827C7.59602 0.242939 7.68108 0.336071 7.73641 0.446792L8.21908 1.41146H12.2191C12.3959 1.41146 12.5655 1.4817 12.6905 1.60672C12.8155 1.73174 12.8857 1.90131 12.8857 2.07812V9.41146C12.8857 9.58827 12.8155 9.75784 12.6905 9.88286C12.5655 10.0079 12.3959 10.0781 12.2191 10.0781H7.96441C7.84063 10.0781 7.71932 10.0435 7.61406 9.97842C7.50879 9.91331 7.42374 9.82018 7.36841 9.70946L6.88574 8.74479H2.21908ZM2.21908 1.41146V7.41146H7.70974L8.37641 8.74479H11.5524V2.74479H7.39508L6.72841 1.41146H2.21908Z"
fill="currentColor"
/>
</svg>
);
}
);
IconSolution.displayName = 'IconSolution';

26
beta/src/components/Icon/IconTerminal.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconTerminal = React.memo<JSX.IntrinsicElements['svg']>(
function IconTerminal({className}) {
return (
<svg
className={className}
width="1em"
height="1em"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M2.40299 2.61279H14.403C14.5798 2.61279 14.7494 2.68303 14.8744 2.80806C14.9994 2.93308 15.0697 3.10265 15.0697 3.27946V13.9461C15.0697 14.1229 14.9994 14.2925 14.8744 14.4175C14.7494 14.5426 14.5798 14.6128 14.403 14.6128H2.40299C2.22618 14.6128 2.05661 14.5426 1.93159 14.4175C1.80657 14.2925 1.73633 14.1229 1.73633 13.9461V3.27946C1.73633 3.10265 1.80657 2.93308 1.93159 2.80806C2.05661 2.68303 2.22618 2.61279 2.40299 2.61279ZM8.403 10.6128V11.9461H12.403V10.6128H8.403ZM6.01233 8.61279L4.12699 10.4981L5.06966 11.4415L7.89833 8.61279L5.06966 5.78413L4.12699 6.72746L6.01233 8.61279Z"
fill="currentColor"
/>
</svg>
);
}
);
IconTerminal.displayName = 'IconTerminal';

24
beta/src/components/Icon/IconTwitter.tsx

@ -0,0 +1,24 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconTwitter = React.memo<JSX.IntrinsicElements['svg']>(
function IconTwitter(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1.33em"
height="1.33em"
fill="currentColor"
{...props}>
<path fill="none" d="M0 0h24v24H0z" />
<path d="M22.162 5.656a8.384 8.384 0 0 1-2.402.658A4.196 4.196 0 0 0 21.6 4c-.82.488-1.719.83-2.656 1.015a4.182 4.182 0 0 0-7.126 3.814 11.874 11.874 0 0 1-8.62-4.37 4.168 4.168 0 0 0-.566 2.103c0 1.45.738 2.731 1.86 3.481a4.168 4.168 0 0 1-1.894-.523v.052a4.185 4.185 0 0 0 3.355 4.101 4.21 4.21 0 0 1-1.89.072A4.185 4.185 0 0 0 7.97 16.65a8.394 8.394 0 0 1-6.191 1.732 11.83 11.83 0 0 0 6.41 1.88c7.693 0 11.9-6.373 11.9-11.9 0-.18-.005-.362-.013-.54a8.496 8.496 0 0 0 2.087-2.165z" />
</svg>
);
}
);
IconTwitter.displayName = 'IconTwitter';

28
beta/src/components/Icon/IconWarning.tsx

@ -0,0 +1,28 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export const IconWarning = React.memo<JSX.IntrinsicElements['svg']>(
function IconWarning({className}) {
return (
<svg
className={className}
width="1.33em"
height="1.33em"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g>
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"
fill="currentColor"
/>
</g>
</svg>
);
}
);
IconWarning.displayName = 'IconWarning';

183
beta/src/components/Layout/Footer.tsx

@ -0,0 +1,183 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import NextLink from 'next/link';
import cn from 'classnames';
import {ExternalLink} from 'components/ExternalLink';
import {IconFacebookCircle} from 'components/Icon/IconFacebookCircle';
import {IconTwitter} from 'components/Icon/IconTwitter';
export function Footer() {
const socialLinkClasses = 'hover:text-primary dark:text-primary-dark';
return (
<>
<div className="self-stretch w-full sm:pl-0 lg:pl-80 sm:pr-0 2xl:pr-80 pl-0 pr-0">
<div className="mx-auto w-full px-5 sm:px-12 md:px-12 pt-10 md:pt-12 lg:pt-10">
<hr className="max-w-7xl mx-auto border-border dark:border-border-dark" />
</div>
<footer className="text-secondary dark:text-secondary-dark py-12 px-5 sm:px-12 md:px-12 sm:py-12 md:py-16 lg:py-14">
<div className="grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-5 gap-x-12 gap-y-8 max-w-7xl mx-auto ">
<ExternalLink
href="https://opensource.fb.com/"
className="col-span-2 sm:col-span-1 justify-items-start w-44 text-left">
<div>
<svg
className="mt-4 mb-4"
width="115"
height="13"
viewBox="0 0 115 13"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M9.12655 0.414727V2.061C9.1323 2.15436 9.06215 2.23355 8.97245 2.23945C8.96555 2.23945 8.95865 2.23945 8.95175 2.23945H2.07259V5.60409H7.75002C7.84087 5.59818 7.91792 5.67027 7.92367 5.76364C7.92367 5.76955 7.92367 5.77664 7.92367 5.78255V7.43C7.92942 7.52336 7.85927 7.60254 7.76842 7.60845C7.76267 7.60845 7.75577 7.60845 7.75002 7.60845H2.07259V12.5827C2.07949 12.6761 2.01049 12.7565 1.92079 12.7635C1.91389 12.7635 1.90699 12.7635 1.90009 12.7635H0.175126C0.084278 12.7695 0.00607958 12.6974 0.000329697 12.6028C0.000329697 12.5969 0.000329697 12.5898 0.000329697 12.5839V0.411182C-0.00542019 0.317818 0.0647284 0.237454 0.156727 0.231545C0.162476 0.231545 0.169376 0.231545 0.175126 0.231545H8.9506C9.04145 0.225636 9.1208 0.296545 9.12655 0.389909C9.1277 0.398182 9.1277 0.406454 9.12655 0.414727Z"
fill="currentColor"
/>
<path
d="M23.1608 12.7637H21.2633C21.1656 12.7708 21.0793 12.701 21.0621 12.6018C20.8102 11.5736 20.5055 10.491 20.157 9.39902H14.3324C13.9874 10.491 13.6792 11.5736 13.4354 12.6018C13.4193 12.701 13.3331 12.7708 13.2353 12.7637H11.4068C11.285 12.7637 11.216 12.6916 11.2505 12.5663C12.3475 8.57648 14.0184 4.17539 15.7078 0.469206C15.7549 0.317933 15.8987 0.219842 16.0528 0.232842H18.5172C18.6713 0.219842 18.815 0.317933 18.8621 0.469206C20.6561 4.37393 22.1465 8.4193 23.3195 12.5663C23.3528 12.6904 23.2827 12.7637 23.1608 12.7637ZM19.513 7.46675C18.8771 5.65857 18.1722 3.85157 17.4431 2.2053H17.0348C16.3115 3.85157 15.5974 5.65857 14.9649 7.46675H19.513Z"
fill="currentColor"
/>
<path
d="M26.2773 6.60636C26.2773 2.71818 28.767 0 32.317 0H32.5781C34.8079 0 36.5317 1.16291 37.4459 2.84464C37.5011 2.91082 37.4942 3.01127 37.4287 3.068C37.416 3.07982 37.4011 3.08927 37.385 3.09518L35.8521 3.874C35.7543 3.94018 35.6221 3.91182 35.5577 3.81136C35.5542 3.80545 35.5508 3.79955 35.5473 3.79364C34.9033 2.64845 33.9373 2.03982 32.5091 2.03982H32.248C30.0102 2.03982 28.4692 3.86455 28.4692 6.513C28.4692 9.16145 29.9837 10.9507 32.248 10.9507H32.5091C33.9718 10.9507 34.8251 10.4414 35.4783 9.66255C35.545 9.57036 35.6681 9.54318 35.7658 9.59991L37.3413 10.387C37.3907 10.4095 37.4241 10.4567 37.4287 10.5123C37.4252 10.5619 37.4068 10.6092 37.3758 10.647C36.4098 12.0971 34.6687 12.9917 32.5459 12.9917H32.2848C28.6716 13 26.2773 10.4449 26.2773 6.60636Z"
fill="currentColor"
/>
<path
d="M51.3171 10.9367V12.5829C51.3228 12.6763 51.2527 12.7567 51.1607 12.7626C51.1549 12.7626 51.1492 12.7626 51.1434 12.7626H42.0011C41.9103 12.7685 41.8332 12.6964 41.8275 12.6042C41.8275 12.5971 41.8275 12.59 41.8275 12.5829V0.4102C41.8217 0.316836 41.8907 0.236473 41.9804 0.230563C41.9873 0.230563 41.9942 0.230563 42.0011 0.230563H50.9859C51.0767 0.224654 51.1549 0.296745 51.1607 0.391291C51.1607 0.3972 51.1607 0.404291 51.1607 0.4102V2.05647C51.1664 2.14984 51.0963 2.22902 51.0066 2.23493C50.9997 2.23493 50.9928 2.23493 50.9859 2.23493H43.8986V5.49202H49.6623C49.7531 5.48611 49.8313 5.5582 49.8371 5.65275C49.8371 5.65866 49.8371 5.66575 49.8371 5.67166V7.3002C49.8417 7.39356 49.7715 7.47393 49.6795 7.47865C49.6738 7.47865 49.668 7.47865 49.6623 7.47865H43.8986V10.7547H51.1434C51.2343 10.7487 51.3125 10.8197 51.3171 10.913C51.3182 10.9213 51.3182 10.9284 51.3171 10.9367Z"
fill="currentColor"
/>
<path
d="M67.058 9.32692C67.058 11.518 65.4216 12.7625 62.5305 12.7625H56.5403C56.4495 12.7684 56.3724 12.6963 56.3667 12.6041C56.3667 12.597 56.3667 12.5899 56.3667 12.5828V0.410105C56.3609 0.316741 56.4299 0.236378 56.5196 0.230469C56.5265 0.230469 56.5334 0.230469 56.5403 0.230469H61.9993C64.8121 0.230469 66.3439 1.37565 66.3439 3.46983C66.3439 4.84783 65.6654 5.75192 64.2889 6.17147C66.222 6.59692 67.058 7.78701 67.058 9.32692ZM61.9556 2.18638H58.4389V5.55456H61.9556C63.5322 5.55456 64.2555 5.02629 64.2555 3.87165C64.2555 2.71701 63.5322 2.18638 61.9556 2.18638ZM64.934 9.13902C64.934 7.97492 64.1854 7.44783 62.5398 7.44783H58.4389V10.8113H62.5398C64.2107 10.8113 64.934 10.3102 64.934 9.13902Z"
fill="currentColor"
/>
<path
d="M70.7057 6.5C70.7057 2.72409 73.1436 0 76.9742 0H77.2353C81.0658 0 83.5038 2.71818 83.5038 6.5C83.5038 10.2818 81.0658 13 77.2353 13H76.9742C73.1413 13 70.7057 10.2747 70.7057 6.5ZM77.2353 10.9555C79.7342 10.9555 81.3096 9.19336 81.3096 6.5C81.3096 3.80664 79.7342 2.04455 77.2353 2.04455H76.9742C74.4753 2.04455 72.8998 3.80664 72.8998 6.5C72.8998 9.19336 74.4753 10.9555 76.9742 10.9555H77.2353Z"
fill="currentColor"
/>
<path
d="M87.0387 6.5C87.0387 2.72409 89.4766 0 93.3072 0H93.5683C97.3988 0 99.8368 2.71818 99.8368 6.5C99.8368 10.2818 97.3988 13 93.5683 13H93.3072C89.4766 13 87.0387 10.2747 87.0387 6.5ZM93.5683 10.9555C96.0672 10.9555 97.6426 9.19336 97.6426 6.5C97.6426 3.80664 96.0672 2.04455 93.5683 2.04455H93.3072C90.8083 2.04455 89.2329 3.80664 89.2329 6.5C89.2329 9.19336 90.8083 10.9555 93.3072 10.9555H93.5683Z"
fill="currentColor"
/>
<path
d="M114.855 12.7637H112.608C112.488 12.7744 112.37 12.7153 112.304 12.6113C110.758 10.7511 109.079 9.01266 107.28 7.41129H106.271V12.5829C106.277 12.6763 106.206 12.7567 106.114 12.7626C106.109 12.7626 106.102 12.7626 106.096 12.7626H104.371C104.28 12.7685 104.203 12.6964 104.197 12.6042C104.197 12.5971 104.197 12.59 104.197 12.5829V0.4102C104.192 0.316836 104.261 0.236473 104.35 0.230563C104.357 0.230563 104.364 0.230563 104.371 0.230563H106.096C106.187 0.224654 106.265 0.296745 106.271 0.391291C106.271 0.3972 106.271 0.404291 106.271 0.4102V5.35375H107.295C108.951 3.83393 110.472 2.16638 111.84 0.370018C111.895 0.279018 111.996 0.227018 112.101 0.235291H114.226C114.33 0.235291 114.383 0.289654 114.383 0.360563C114.378 0.411382 114.356 0.458654 114.322 0.495291C112.682 2.59893 110.861 4.54538 108.88 6.31102C111.065 8.21375 113.095 10.2961 114.948 12.538C115.046 12.655 115 12.7637 114.855 12.7637Z"
fill="currentColor"
/>
</svg>
Open Source
</div>
<div className="text-xs text-left mt-2 pr-0.5">
&copy;{new Date().getFullYear()}
</div>
</ExternalLink>
<div className="flex flex-col">
<FooterLink href="/learn" isHeader={true}>
Learn React
</FooterLink>
<FooterLink href="/learn/">Quick Start</FooterLink>
<FooterLink href="/learn/installation">Installation</FooterLink>
<FooterLink href="/learn/describing-the-ui">
Describing the UI
</FooterLink>
<FooterLink href="/learn/adding-interactivity">
Adding Interactivity
</FooterLink>
<FooterLink href="/learn/managing-state">
Managing State
</FooterLink>
<FooterLink href="/learn/escape-hatches">
Escape Hatches
</FooterLink>
</div>
<div className="flex flex-col">
<FooterLink href="/reference" isHeader={true}>
API Reference
</FooterLink>
<FooterLink href="/reference">React APIs</FooterLink>
<FooterLink href="/reference/reactdom">React DOM APIs</FooterLink>
</div>
<div className="flex flex-col sm:col-start-2 xl:col-start-4">
<FooterLink href="/" isHeader={true}>
Community
</FooterLink>
<FooterLink href="https://github.com/facebook/react/blob/main/CODE_OF_CONDUCT.md">
Code of Conduct
</FooterLink>
<FooterLink href="/community/acknowledgements">
Acknowledgements
</FooterLink>
<FooterLink href="/community/meet-the-team">
Meet the Team
</FooterLink>
{/* <FooterLink href="/">Community Resources</FooterLink> */}
</div>
<div className="flex flex-col">
<FooterLink isHeader={true}>More</FooterLink>
{/* <FooterLink href="/">Tutorial</FooterLink> */}
{/* <FooterLink href="/">Blog</FooterLink> */}
{/* <FooterLink href="/">Acknowledgements</FooterLink> */}
<FooterLink href="https://reactnative.dev/">
React Native
</FooterLink>
<FooterLink href="https://opensource.facebook.com/legal/privacy">
Privacy
</FooterLink>
<FooterLink href="https://opensource.fb.com/legal/terms/">
Terms
</FooterLink>
<div className="flex flex-row mt-8 gap-x-2">
<ExternalLink
href="https://www.facebook.com/react"
className={socialLinkClasses}>
<IconFacebookCircle />
</ExternalLink>
<ExternalLink
href="https://twitter.com/reactjs"
className={socialLinkClasses}>
<IconTwitter />
</ExternalLink>
</div>
</div>
</div>
</footer>
</div>
</>
);
}
function FooterLink({
href,
children,
isHeader = false,
}: {
href?: string;
children: React.ReactNode;
isHeader?: boolean;
}) {
const classes = cn('border-b inline-block border-transparent', {
'text-sm text-primary dark:text-primary-dark': !isHeader,
'text-md text-secondary dark:text-secondary-dark my-2 font-bold': isHeader,
'hover:border-gray-10': href,
});
if (!href) {
return <div className={classes}>{children}</div>;
}
if (href.startsWith('https://')) {
return (
<div>
<ExternalLink href={href} className={classes}>
{children}
</ExternalLink>
</div>
);
}
return (
<div>
<NextLink href={href}>
<a className={classes}>{children}</a>
</NextLink>
</div>
);
}

26
beta/src/components/Layout/LayoutAPI.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import sidebarReference from 'sidebarReference.json';
import {MarkdownPage, MarkdownProps} from './MarkdownPage';
import {Page} from './Page';
import {RouteItem} from './useRouteMeta';
interface PageFrontmatter {
title: string;
status: string;
}
export default function withAPI(p: PageFrontmatter) {
function LayoutAPI(props: MarkdownProps<PageFrontmatter>) {
return <MarkdownPage {...props} meta={p} />;
}
LayoutAPI.appShell = AppShell;
return LayoutAPI;
}
function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={sidebarReference as RouteItem} {...props} />;
}

26
beta/src/components/Layout/LayoutHome.tsx

@ -0,0 +1,26 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import sidebarHome from 'sidebarHome.json';
import {MarkdownPage, MarkdownProps} from './MarkdownPage';
import {Page} from './Page';
import {RouteItem} from './useRouteMeta';
interface PageFrontmatter {
title: string;
status: string;
}
export default function withDocs(p: PageFrontmatter) {
function LayoutHome(props: MarkdownProps<PageFrontmatter>) {
return <MarkdownPage {...props} meta={p} />;
}
LayoutHome.appShell = AppShell;
return LayoutHome;
}
function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={sidebarHome as RouteItem} {...props} />;
}

24
beta/src/components/Layout/LayoutLearn.tsx

@ -0,0 +1,24 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {MarkdownPage, MarkdownProps} from './MarkdownPage';
import {RouteItem} from 'components/Layout/useRouteMeta';
import {Page} from './Page';
import sidebarLearn from '../../sidebarLearn.json';
interface PageFrontmatter {
title: string;
}
export default function withLearn(meta: PageFrontmatter) {
function LayoutLearn(props: MarkdownProps<PageFrontmatter>) {
return <MarkdownPage {...props} meta={meta} />;
}
LayoutLearn.appShell = AppShell;
return LayoutLearn;
}
function AppShell(props: {children: React.ReactNode}) {
return <Page {...props} routeTree={sidebarLearn as RouteItem} />;
}

117
beta/src/components/Layout/LayoutPost.tsx

@ -0,0 +1,117 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import {MDXProvider} from '@mdx-js/react';
import recentPostsRouteTree from 'blogIndexRecent.json';
import {DocsPageFooter} from 'components/DocsFooter';
import {ExternalLink} from 'components/ExternalLink';
import {MDXComponents} from 'components/MDX/MDXComponents';
import {Seo} from 'components/Seo';
import {Toc} from 'components/Layout/Toc';
import format from 'date-fns/format';
import {useRouter} from 'next/router';
import * as React from 'react';
import {getAuthor} from 'utils/getAuthor';
import toCommaSeparatedList from 'utils/toCommaSeparatedList';
import {Page} from './Page';
import {RouteItem, useRouteMeta} from './useRouteMeta';
import {useTwitter} from './useTwitter';
interface PageFrontmatter {
id?: string;
title: string;
author: string[];
date?: string;
}
interface LayoutPostProps {
/** Sidebar/Nav */
routes: RouteItem[];
/** Markdown frontmatter */
meta: PageFrontmatter;
/** The mdx */
children: React.ReactNode;
}
/** Return the date of the current post given the path */
function getDateFromPath(path: string) {
// All paths are /blog/year/month/day/title
const [year, month, day] = path
.substr(1) // first `/`
.split('/') // make an array
.slice(1) // ignore blog
.map((i) => parseInt(i, 10)); // convert to numbers
return {
date: format(new Date(year, month, day), 'MMMM dd, yyyy'),
dateTime: [year, month, day].join('-'),
};
}
function LayoutPost({meta, children}: LayoutPostProps) {
const {pathname} = useRouter();
const {date, dateTime} = getDateFromPath(pathname);
const {route, nextRoute, prevRoute} = useRouteMeta();
const anchors = React.Children.toArray(children)
.filter(
(child: any) =>
child.props?.mdxType && ['h2', 'h3'].includes(child.props.mdxType)
)
.map((child: any) => ({
url: '#' + child.props.id,
depth: parseInt(child.props.mdxType.replace('h', ''), 0),
text: child.props.children,
}));
useTwitter();
return (
<>
<div className="w-full px-12">
<div className="h-full mx-auto max-w-4xl relative pt-16 w-full overflow-x-hidden">
<Seo title={meta.title} />
<h1 className="mb-6 pt-8 text-4xl md:text-5xl font-bold leading-snug tracking-tight text-primary dark:text-primary-dark">
{meta.title}
</h1>
<p className="mb-6 text-lgtext-secondary dark:text-secondary-dark">
By{' '}
{toCommaSeparatedList(meta.author, (author) => (
<ExternalLink
href={getAuthor(author).url}
className="text-link dark:text-link-dark underline font-bold">
{getAuthor(author).name}
</ExternalLink>
))}
<span className="mx-2">·</span>
<span className="lead inline-flex text-gray-50">
<time dateTime={dateTime}>{date}</time>
</span>
</p>
<MDXProvider components={MDXComponents}>{children}</MDXProvider>
<DocsPageFooter
route={route}
nextRoute={nextRoute}
prevRoute={prevRoute}
/>
</div>
</div>
<div className="w-full lg:max-w-xs h-full hidden 2xl:block">
<Toc headings={anchors} />
</div>
</>
);
}
function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={recentPostsRouteTree as RouteItem} {...props} />;
}
export default function withLayoutPost(meta: any) {
function LayoutPostWrapper(props: LayoutPostProps) {
return <LayoutPost {...props} meta={meta} />;
}
LayoutPostWrapper.appShell = AppShell;
return LayoutPostWrapper;
}

154
beta/src/components/Layout/MarkdownPage.tsx

@ -0,0 +1,154 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {MDXProvider} from '@mdx-js/react';
import {DocsPageFooter} from 'components/DocsFooter';
import {MDXComponents} from 'components/MDX/MDXComponents';
import {Seo} from 'components/Seo';
import PageHeading from 'components/PageHeading';
import {useRouteMeta} from './useRouteMeta';
import {Toc} from './Toc';
export interface MarkdownProps<Frontmatter> {
meta: Frontmatter & {description?: string};
children?: React.ReactNode;
}
function MaxWidth({children}: {children: any}) {
return <div className="max-w-4xl ml-0 2xl:mx-auto">{children}</div>;
}
export function MarkdownPage<
T extends {title: string; status?: string} = {title: string; status?: string}
>({children, meta}: MarkdownProps<T>) {
const {route, nextRoute, prevRoute} = useRouteMeta();
const title = meta.title || route?.title || '';
const description = meta.description || route?.description || '';
let anchors: Array<{
url: string;
text: React.ReactNode;
depth: number;
}> = React.Children.toArray(children)
.filter((child: any) => {
if (child.props?.mdxType) {
return ['h1', 'h2', 'h3', 'Challenges', 'Recipes', 'Recap'].includes(
child.props.mdxType
);
}
return false;
})
.map((child: any) => {
if (child.props.mdxType === 'Challenges') {
return {
url: '#challenges',
depth: 0,
text: 'Challenges',
};
}
if (child.props.mdxType === 'Recipes') {
return {
url: '#recipes',
depth: 0,
text: 'Recipes',
};
}
if (child.props.mdxType === 'Recap') {
return {
url: '#recap',
depth: 0,
text: 'Recap',
};
}
return {
url: '#' + child.props.id,
depth:
(child.props?.mdxType &&
parseInt(child.props.mdxType.replace('h', ''), 0)) ??
0,
text: child.props.children,
};
});
if (anchors.length > 0) {
anchors.unshift({
depth: 1,
text: 'Overview',
url: '#',
});
}
if (!route) {
console.error('This page was not added to one of the sidebar JSON files.');
}
const isHomePage = route?.path === '/';
// Auto-wrap everything except a few types into
// <MaxWidth> wrappers. Keep reusing the same
// wrapper as long as we can until we meet
// a full-width section which interrupts it.
let fullWidthTypes = [
'Sandpack',
'APIAnatomy',
'FullWidth',
'Illustration',
'IllustrationBlock',
'Challenges',
'Recipes',
];
let wrapQueue: React.ReactNode[] = [];
let finalChildren: React.ReactNode[] = [];
function flushWrapper(key: string | number) {
if (wrapQueue.length > 0) {
finalChildren.push(<MaxWidth key={key}>{wrapQueue}</MaxWidth>);
wrapQueue = [];
}
}
function handleChild(child: any, key: string | number) {
if (child == null) {
return;
}
if (typeof child !== 'object') {
wrapQueue.push(child);
return;
}
if (fullWidthTypes.includes(child.props.mdxType)) {
flushWrapper(key);
finalChildren.push(child);
} else {
wrapQueue.push(child);
}
}
React.Children.forEach(children, handleChild);
flushWrapper('last');
return (
<article className="h-full mx-auto relative w-screen min-w-0">
<div className="lg:pt-0 pt-20 pl-0 lg:pl-80 2xl:px-80 ">
<Seo title={title} />
{!isHomePage && (
<PageHeading
title={title}
description={description}
tags={route?.tags}
/>
)}
<div className="px-5 sm:px-12">
<div className="max-w-7xl mx-auto">
<MDXProvider components={MDXComponents}>
{finalChildren}
</MDXProvider>
</div>
<DocsPageFooter
route={route}
nextRoute={nextRoute}
prevRoute={prevRoute}
/>
</div>
</div>
<div className="w-full lg:max-w-xs hidden 2xl:block">
{!isHomePage && anchors.length > 0 && <Toc headings={anchors} />}
</div>
</article>
);
}

86
beta/src/components/Layout/Nav/MobileNav.tsx

@ -0,0 +1,86 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {RouteItem} from 'components/Layout/useRouteMeta';
import {useRouter} from 'next/router';
import {SidebarRouteTree} from '../Sidebar';
import sidebarHome from '../../../sidebarHome.json';
import sidebarLearn from '../../../sidebarLearn.json';
import sidebarReference from '../../../sidebarReference.json';
function inferSection(pathname: string): 'learn' | 'reference' | 'home' {
if (pathname.startsWith('/learn')) {
return 'learn';
} else if (pathname.startsWith('/reference')) {
return 'reference';
} else {
return 'home';
}
}
export function MobileNav() {
const {pathname} = useRouter();
const [section, setSection] = React.useState(() => inferSection(pathname));
let tree = null;
switch (section) {
case 'home':
tree = sidebarHome.routes[0];
break;
case 'learn':
tree = sidebarLearn.routes[0];
break;
case 'reference':
tree = sidebarReference.routes[0];
break;
}
return (
<>
<div className="sticky top-0 px-5 mb-2 bg-wash dark:bg-wash-dark flex justify-end border-b border-border dark:border-border-dark items-center self-center w-full z-10">
<TabButton
isActive={section === 'home'}
onClick={() => setSection('home')}>
Home
</TabButton>
<TabButton
isActive={section === 'learn'}
onClick={() => setSection('learn')}>
Learn
</TabButton>
<TabButton
isActive={section === 'reference'}
onClick={() => setSection('reference')}>
API
</TabButton>
</div>
<SidebarRouteTree routeTree={tree as RouteItem} isMobile={true} />
</>
);
}
function TabButton({
children,
onClick,
isActive,
}: {
children: any;
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
isActive: boolean;
}) {
const classes = cn(
'inline-flex items-center w-full border-b-2 justify-center text-base leading-9 px-3 py-0.5 hover:text-link hover:gray-5',
{
'text-link dark:text-link-dark dark:border-link-dark border-link font-bold': isActive,
'border-transparent': !isActive,
}
);
return (
<button className={classes} onClick={onClick}>
{children}
</button>
);
}

193
beta/src/components/Layout/Nav/Nav.tsx

@ -0,0 +1,193 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import NextLink from 'next/link';
import {useRouter} from 'next/router';
import {IconClose} from 'components/Icon/IconClose';
import {IconHamburger} from 'components/Icon/IconHamburger';
import {Search} from 'components/Search';
import {MenuContext} from 'components/useMenu';
import {Logo} from '../../Logo';
import NavLink from './NavLink';
const feedbackIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24">
<g fill="none" fillRule="evenodd" transform="translate(-444 -204)">
<g fill="currentColor" transform="translate(354.5 205)">
<path d="M102.75 14C102.75 14.6905 102.1905 15.25 101.5 15.25 100.8095 15.25 100.25 14.6905 100.25 14 100.25 13.3095 100.8095 12.75 101.5 12.75 102.1905 12.75 102.75 13.3095 102.75 14M101 5.25L101.5 11 102 5.25C102 5.25 102 4.75 101.5 4.75 101 4.75 101 5.25 101 5.25" />
<path
fillRule="nonzero"
d="M100.25282,5.31497221 L100.75282,11.0649722 C100.832243,11.9783426 102.167757,11.9783426 102.24718,11.0649722 L102.74718,5.31497221 L102.75,5.25 C102.75,5.08145956 102.716622,4.88119374 102.60832,4.6645898 C102.407353,4.2626558 102.01337,4 101.5,4 C100.98663,4 100.592647,4.2626558 100.39168,4.6645898 C100.283378,4.88119374 100.25,5.08145956 100.25,5.25 L100.25282,5.31497221 Z M101.249053,5.22834322 L101.24717,5.25 L101.75283,5.25 L101.750947,5.22834322 L101.5,5.20652174 L101.249053,5.22834322 Z M101.25,5.25 L101.75,5.25 C101.75,5.29354044 101.747872,5.30630626 101.73332,5.3354102 C101.688603,5.4248442 101.57587,5.5 101.5,5.5 C101.42413,5.5 101.311397,5.4248442 101.26668,5.3354102 C101.252128,5.30630626 101.25,5.29354044 101.25,5.25 Z"
/>
<path
fillRule="nonzero"
d="M96.2928885,18.5085 L109.75,18.5085 C111.268908,18.5085 112.5085,17.268908 112.5085,15.75 L112.5085,3.25 C112.5085,1.73109202 111.268908,0.4915 109.75,0.4915 L93.25,0.4915 C91.731092,0.4915 90.4915,1.73109202 90.4915,3.25 L90.4915,21.5 C90.4915,22.3943136 91.5519083,22.8250723 92.1764221,22.2491522 L92.230357,22.1957095 L96.2928885,18.5085 Z M92.0085,3.25 C92.0085,2.56890798 92.568908,2.0085 93.25,2.0085 L109.75,2.0085 C110.431092,2.0085 110.9915,2.56890798 110.9915,3.25 L110.9915,15.75 C110.9915,16.431092 110.431092,16.9915 109.75,16.9915 L96,16.9915 C95.8115227,16.9915 95.6297966,17.0616721 95.4902321,17.1883427 L92.0085,20.3484106 L92.0085,3.25 Z"
/>
</g>
<polygon points="444 228 468 228 468 204 444 204" />
</g>
</svg>
);
const darkIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24">
<g fill="none" fillRule="evenodd" transform="translate(-444 -204)">
<path
fill="currentColor"
fillRule="nonzero"
d="M102,21 C102,18.1017141 103.307179,15.4198295 105.51735,13.6246624 C106.001939,13.2310647 105.821611,12.4522936 105.21334,12.3117518 C104.322006,12.1058078 103.414758,12 102.5,12 C95.8722864,12 90.5,17.3722864 90.5,24 C90.5,30.6277136 95.8722864,36 102.5,36 C106.090868,36 109.423902,34.4109093 111.690274,31.7128995 C112.091837,31.2348572 111.767653,30.5041211 111.143759,30.4810139 C106.047479,30.2922628 102,26.1097349 102,21 Z M102.5,34.5 C96.7007136,34.5 92,29.7992864 92,24 C92,18.2007136 96.7007136,13.5 102.5,13.5 C102.807386,13.5 103.113925,13.5136793 103.419249,13.5407785 C101.566047,15.5446378 100.5,18.185162 100.5,21 C100.5,26.3198526 104.287549,30.7714322 109.339814,31.7756638 L109.516565,31.8092927 C107.615276,33.5209452 105.138081,34.5 102.5,34.5 Z"
transform="translate(354.5 192)"
/>
<polygon points="444 228 468 228 468 204 444 204" />
</g>
</svg>
);
const lightIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none" fillRule="evenodd" transform="translate(-444 -204)">
<g fill="currentColor" transform="translate(354 144)">
<path
fillRule="nonzero"
d="M108.5 24C108.5 27.5902136 105.590214 30.5 102 30.5 98.4097864 30.5 95.5 27.5902136 95.5 24 95.5 20.4097864 98.4097864 17.5 102 17.5 105.590214 17.5 108.5 20.4097864 108.5 24zM107 24C107 21.2382136 104.761786 19 102 19 99.2382136 19 97 21.2382136 97 24 97 26.7617864 99.2382136 29 102 29 104.761786 29 107 26.7617864 107 24zM101 12.75L101 14.75C101 15.1642136 101.335786 15.5 101.75 15.5 102.164214 15.5 102.5 15.1642136 102.5 14.75L102.5 12.75C102.5 12.3357864 102.164214 12 101.75 12 101.335786 12 101 12.3357864 101 12.75zM95.7255165 14.6323616L96.7485165 16.4038616C96.9556573 16.7625614 97.4143618 16.8854243 97.7730616 16.6782835 98.1317614 16.4711427 98.2546243 16.0124382 98.0474835 15.6537384L97.0244835 13.8822384C96.8173427 13.5235386 96.3586382 13.4006757 95.9999384 13.6078165 95.6412386 13.8149573 95.5183757 14.2736618 95.7255165 14.6323616zM91.8822384 19.0244835L93.6537384 20.0474835C94.0124382 20.2546243 94.4711427 20.1317614 94.6782835 19.7730616 94.8854243 19.4143618 94.7625614 18.9556573 94.4038616 18.7485165L92.6323616 17.7255165C92.2736618 17.5183757 91.8149573 17.6412386 91.6078165 17.9999384 91.4006757 18.3586382 91.5235386 18.8173427 91.8822384 19.0244835zM90.75 25L92.75 25C93.1642136 25 93.5 24.6642136 93.5 24.25 93.5 23.8357864 93.1642136 23.5 92.75 23.5L90.75 23.5C90.3357864 23.5 90 23.8357864 90 24.25 90 24.6642136 90.3357864 25 90.75 25zM92.6323616 30.2744835L94.4038616 29.2514835C94.7625614 29.0443427 94.8854243 28.5856382 94.6782835 28.2269384 94.4711427 27.8682386 94.0124382 27.7453757 93.6537384 27.9525165L91.8822384 28.9755165C91.5235386 29.1826573 91.4006757 29.6413618 91.6078165 30.0000616 91.8149573 30.3587614 92.2736618 30.4816243 92.6323616 30.2744835zM97.0244835 34.1177616L98.0474835 32.3462616C98.2546243 31.9875618 98.1317614 31.5288573 97.7730616 31.3217165 97.4143618 31.1145757 96.9556573 31.2374386 96.7485165 31.5961384L95.7255165 33.3676384C95.5183757 33.7263382 95.6412386 34.1850427 95.9999384 34.3921835 96.3586382 34.5993243 96.8173427 34.4764614 97.0244835 34.1177616zM103 35.25L103 33.25C103 32.8357864 102.664214 32.5 102.25 32.5 101.835786 32.5 101.5 32.8357864 101.5 33.25L101.5 35.25C101.5 35.6642136 101.835786 36 102.25 36 102.664214 36 103 35.6642136 103 35.25zM108.274483 33.3676384L107.251483 31.5961384C107.044343 31.2374386 106.585638 31.1145757 106.226938 31.3217165 105.868239 31.5288573 105.745376 31.9875618 105.952517 32.3462616L106.975517 34.1177616C107.182657 34.4764614 107.641362 34.5993243 108.000062 34.3921835 108.358761 34.1850427 108.481624 33.7263382 108.274483 33.3676384zM112.117762 28.9755165L110.346262 27.9525165C109.987562 27.7453757 109.528857 27.8682386 109.321717 28.2269384 109.114576 28.5856382 109.237439 29.0443427 109.596138 29.2514835L111.367638 30.2744835C111.726338 30.4816243 112.185043 30.3587614 112.392183 30.0000616 112.599324 29.6413618 112.476461 29.1826573 112.117762 28.9755165zM113.25 23L111.25 23C110.835786 23 110.5 23.3357864 110.5 23.75 110.5 24.1642136 110.835786 24.5 111.25 24.5L113.25 24.5C113.664214 24.5 114 24.1642136 114 23.75 114 23.3357864 113.664214 23 113.25 23zM111.367638 17.7255165L109.596138 18.7485165C109.237439 18.9556573 109.114576 19.4143618 109.321717 19.7730616 109.528857 20.1317614 109.987562 20.2546243 110.346262 20.0474835L112.117762 19.0244835C112.476461 18.8173427 112.599324 18.3586382 112.392183 17.9999384 112.185043 17.6412386 111.726338 17.5183757 111.367638 17.7255165zM106.975517 13.8822384L105.952517 15.6537384C105.745376 16.0124382 105.868239 16.4711427 106.226938 16.6782835 106.585638 16.8854243 107.044343 16.7625614 107.251483 16.4038616L108.274483 14.6323616C108.481624 14.2736618 108.358761 13.8149573 108.000062 13.6078165 107.641362 13.4006757 107.182657 13.5235386 106.975517 13.8822384z"
transform="translate(0 48)"
/>
<path
d="M98.6123,60.1372 C98.6123,59.3552 98.8753,58.6427 99.3368,58.0942 C99.5293,57.8657 99.3933,57.5092 99.0943,57.5017 C99.0793,57.5012 99.0633,57.5007 99.0483,57.5007 C97.1578,57.4747 95.5418,59.0312 95.5008,60.9217 C95.4578,62.8907 97.0408,64.5002 98.9998,64.5002 C99.7793,64.5002 100.4983,64.2452 101.0798,63.8142 C101.3183,63.6372 101.2358,63.2627 100.9478,63.1897 C99.5923,62.8457 98.6123,61.6072 98.6123,60.1372"
transform="translate(3 11)"
/>
</g>
<polygon points="444 228 468 228 468 204 444 204" />
</g>
</svg>
);
function inferSection(pathname: string): 'learn' | 'reference' | 'home' {
if (pathname.startsWith('/learn')) {
return 'learn';
} else if (pathname.startsWith('/reference')) {
return 'reference';
} else {
return 'home';
}
}
export default function Nav() {
const {pathname} = useRouter();
const {isOpen, toggleOpen} = React.useContext(MenuContext);
// TODO: persist
// TODO: respect system pref
const [isDark, setIsDark] = React.useState(() => {
if (typeof document === 'undefined') {
return false;
}
return document.documentElement.classList.contains('dark');
});
const section = inferSection(pathname);
function handleFeedback() {
const nodes: any = document.querySelectorAll(
'#_hj_feedback_container button'
);
if (nodes.length > 0) {
nodes[nodes.length - 1].click();
} else {
window.location.href =
'https://github.com/reactjs/reactjs.org/issues/3308';
}
}
return (
<nav className="sticky top-0 items-center w-full flex lg:block justify-between bg-wash dark:bg-wash-dark pt-0 lg:pt-4 pr-5 lg:px-5 z-50">
<div className="xl:w-full xl:max-w-xs flex items-center">
<button
type="button"
aria-label="Menu"
onClick={toggleOpen}
className={cn('flex lg:hidden items-center h-full px-4', {
'text-link dark:text-link-dark mr-0': isOpen,
})}>
{!isOpen ? <IconHamburger /> : <IconClose />}
</button>
<NextLink href="/">
<a className="inline-flex text-l font-normal items-center text-primary dark:text-primary-dark py-1 mr-0 sm:mr-3 whitespace-nowrap">
<Logo className="text-sm mr-2 w-8 h-8 text-link dark:text-link-dark" />
React Docs
</a>
</NextLink>
<div className="lg:w-full leading-loose hidden sm:flex flex-initial items-center h-auto pr-5 lg:pr-5 pt-0.5">
<div className="px-1 mb-px bg-highlight dark:bg-highlight-dark rounded text-link dark:text-link-dark uppercase font-bold tracking-wide text-xs whitespace-nowrap">
Beta
</div>
</div>
<button
type="button"
aria-label={isDark ? 'Use Light Mode' : 'Use Dark Mode'}
onClick={() => {
if (isDark) {
document.documentElement.classList.remove('dark');
setIsDark(false);
} else {
document.documentElement.classList.add('dark');
setIsDark(true);
}
}}
className="hidden lg:flex items-center h-full pr-2">
{isDark ? lightIcon : darkIcon}
</button>
</div>
<div className="px-0 pt-2 w-full 2xl:max-w-xs hidden lg:flex items-center self-center border-b-0 lg:border-b border-border dark:border-border-dark">
<NavLink href="/" isActive={section === 'home'}>
Home
</NavLink>
<NavLink href="/learn" isActive={section === 'learn'}>
Learn
</NavLink>
<NavLink href="/reference" isActive={section === 'reference'}>
API
</NavLink>
</div>
<div className="flex my-4 h-10 mx-0 w-full lg:hidden justify-end slg:max-w-sm">
<Search />
<button
type="button"
className="inline-flex lg:hidden items-center p-1 ml-4 lg:ml-6 relative top-px"
onClick={handleFeedback}>
{feedbackIcon}
</button>
<button
type="button"
aria-label={isDark ? 'Use Light Mode' : 'Use Dark Mode'}
onClick={() => {
if (isDark) {
document.documentElement.classList.remove('dark');
setIsDark(false);
} else {
document.documentElement.classList.add('dark');
setIsDark(true);
}
}}
className="flex lg:hidden items-center p-1 h-full ml-4 lg:ml-6">
{isDark ? lightIcon : darkIcon}
</button>
</div>
</nav>
);
}

39
beta/src/components/Layout/Nav/NavButtonLink.tsx

@ -0,0 +1,39 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import NextLink from 'next/link';
import {ExternalLink} from 'components/ExternalLink';
interface NavButtonLinkProps {
href: string;
children: React.ReactNode;
className?: string;
}
export default function NavButtonLink({
href,
children,
className,
}: NavButtonLinkProps) {
const classes = cn(
'mr-3 text-xs items-center border-b border-link border-opacity-0 hover:text-link hover:border-opacity-100',
className
);
if (href.startsWith('https://')) {
return (
<ExternalLink href={href} className={classes}>
{children}
</ExternalLink>
);
}
return (
<NextLink href={href}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a className={classes}>{children}</a>
</NextLink>
);
}

38
beta/src/components/Layout/Nav/NavLink.tsx

@ -0,0 +1,38 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {ExternalLink} from 'components/ExternalLink';
import NextLink from 'next/link';
interface NavLinkProps {
href: string;
children: React.ReactNode;
isActive: boolean;
}
export default function NavLink({href, children, isActive}: NavLinkProps) {
const classes = cn(
{
'text-link border-link dark:text-link-dark dark:border-link-dark font-bold': isActive,
},
{'border-transparent': !isActive},
'inline-flex w-full items-center border-b-2 justify-center text-base leading-9 px-3 py-0.5 hover:text-link dark:hover:text-link-dark whitespace-nowrap'
);
if (href.startsWith('https://')) {
return (
<ExternalLink href={href} className={classes}>
{children}
</ExternalLink>
);
}
return (
<NextLink href={href}>
<a className={classes}>{children}</a>
</NextLink>
);
}

5
beta/src/components/Layout/Nav/index.tsx

@ -0,0 +1,5 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
export {default as Nav} from './Nav';

40
beta/src/components/Layout/Page.tsx

@ -0,0 +1,40 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import {MenuProvider} from 'components/useMenu';
import * as React from 'react';
import {Nav} from './Nav';
import {RouteItem, SidebarContext} from './useRouteMeta';
import {Sidebar} from './Sidebar';
import {Footer} from './Footer';
interface PageProps {
children: React.ReactNode;
routeTree: RouteItem;
}
export function Page({routeTree, children}: PageProps) {
return (
<MenuProvider>
<SidebarContext.Provider value={routeTree}>
<div className="h-auto lg:h-screen flex flex-row">
<div className="h-auto lg:h-full overflow-y-scroll fixed flex flex-row lg:flex-col py-0 top-0 left-0 right-0 lg:max-w-xs w-full shadow lg:shadow-none z-50">
<Nav />
<Sidebar />
</div>
<div className="flex flex-1 h-full self-stretch">
<div className="w-full min-w-0">
<main
className="flex flex-1 self-stretch flex-col items-end"
style={{justifyContent: 'space-around'}}>
{children}
<Footer />
</main>
</div>
</div>
</div>
</SidebarContext.Provider>
</MenuProvider>
);
}

96
beta/src/components/Layout/Sidebar/Sidebar.tsx

@ -0,0 +1,96 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {SidebarContext} from 'components/Layout/useRouteMeta';
import {MenuContext} from 'components/useMenu';
import {useMediaQuery} from '../useMediaQuery';
import {SidebarRouteTree} from './SidebarRouteTree';
import {Search} from 'components/Search';
import {Button} from 'components/Button';
import {MobileNav} from '../Nav/MobileNav';
const SIDEBAR_BREAKPOINT = 1023;
export function Sidebar({isMobileOnly}: {isMobileOnly?: boolean}) {
const {menuRef, isOpen} = React.useContext(MenuContext);
const isMobileSidebar = useMediaQuery(SIDEBAR_BREAKPOINT);
let routeTree = React.useContext(SidebarContext);
const isHidden = isMobileOnly && !isMobileSidebar;
// HACK. Fix up the data structures instead.
if ((routeTree as any).routes.length === 1) {
routeTree = (routeTree as any).routes[0];
}
function handleFeedback() {
const nodes: any = document.querySelectorAll(
'#_hj_feedback_container button'
);
if (nodes.length > 0) {
nodes[nodes.length - 1].click();
} else {
window.location.href =
'https://github.com/reactjs/reactjs.org/issues/3308';
}
}
const feedbackIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
className="mr-2"
viewBox="0 0 24 24">
<g fill="none" fillRule="evenodd" transform="translate(-444 -204)">
<g fill="currentColor" transform="translate(354.5 205)">
<path d="M102.75 14C102.75 14.6905 102.1905 15.25 101.5 15.25 100.8095 15.25 100.25 14.6905 100.25 14 100.25 13.3095 100.8095 12.75 101.5 12.75 102.1905 12.75 102.75 13.3095 102.75 14M101 5.25L101.5 11 102 5.25C102 5.25 102 4.75 101.5 4.75 101 4.75 101 5.25 101 5.25" />
<path
fillRule="nonzero"
d="M100.25282,5.31497221 L100.75282,11.0649722 C100.832243,11.9783426 102.167757,11.9783426 102.24718,11.0649722 L102.74718,5.31497221 L102.75,5.25 C102.75,5.08145956 102.716622,4.88119374 102.60832,4.6645898 C102.407353,4.2626558 102.01337,4 101.5,4 C100.98663,4 100.592647,4.2626558 100.39168,4.6645898 C100.283378,4.88119374 100.25,5.08145956 100.25,5.25 L100.25282,5.31497221 Z M101.249053,5.22834322 L101.24717,5.25 L101.75283,5.25 L101.750947,5.22834322 L101.5,5.20652174 L101.249053,5.22834322 Z M101.25,5.25 L101.75,5.25 C101.75,5.29354044 101.747872,5.30630626 101.73332,5.3354102 C101.688603,5.4248442 101.57587,5.5 101.5,5.5 C101.42413,5.5 101.311397,5.4248442 101.26668,5.3354102 C101.252128,5.30630626 101.25,5.29354044 101.25,5.25 Z"
/>
<path
fillRule="nonzero"
d="M96.2928885,18.5085 L109.75,18.5085 C111.268908,18.5085 112.5085,17.268908 112.5085,15.75 L112.5085,3.25 C112.5085,1.73109202 111.268908,0.4915 109.75,0.4915 L93.25,0.4915 C91.731092,0.4915 90.4915,1.73109202 90.4915,3.25 L90.4915,21.5 C90.4915,22.3943136 91.5519083,22.8250723 92.1764221,22.2491522 L92.230357,22.1957095 L96.2928885,18.5085 Z M92.0085,3.25 C92.0085,2.56890798 92.568908,2.0085 93.25,2.0085 L109.75,2.0085 C110.431092,2.0085 110.9915,2.56890798 110.9915,3.25 L110.9915,15.75 C110.9915,16.431092 110.431092,16.9915 109.75,16.9915 L96,16.9915 C95.8115227,16.9915 95.6297966,17.0616721 95.4902321,17.1883427 L92.0085,20.3484106 L92.0085,3.25 Z"
/>
</g>
<polygon points="444 228 468 228 468 204 444 204" />
</g>
</svg>
);
return (
<aside
className={cn(
`lg:flex-grow lg:flex flex-col w-full pt-4 pb-8 lg:pb-0 lg:max-w-xs fixed lg:sticky bg-wash dark:bg-wash-dark z-10`,
isOpen ? 'block z-40' : 'hidden lg:block'
)}
aria-hidden={isHidden ? 'true' : 'false'}
style={{
top: 0,
visibility: isHidden ? 'hidden' : undefined,
}}>
<div className="px-5">
<Search />
</div>
<nav
role="navigation"
ref={menuRef}
style={{'--bg-opacity': '.2'} as React.CSSProperties} // Need to cast here because CSS vars aren't considered valid in TS types (cuz they could be anything)
className="w-full h-screen lg:h-auto flex-grow pr-0 lg:pr-5 pt-6 pb-16 lg:pb-0 lg:py-6 md:pt-4 lg:pt-4 overflow-y-scroll lg:overflow-y-auto scrolling-touch scrolling-gpu">
{isMobileSidebar ? (
<MobileNav />
) : (
<SidebarRouteTree routeTree={routeTree} />
)}
</nav>
<div className="px-5 py-3 sticky bottom-0 lg:px-5 w-full hidden md:flex items-center bg-gradient-to-t from-wash dark:from-wash-dark">
<Button
className="w-full text-center justify-center"
onClick={handleFeedback}>
{feedbackIcon} Feedback
</Button>
</div>
</aside>
);
}

57
beta/src/components/Layout/Sidebar/SidebarButton.tsx

@ -0,0 +1,57 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {IconNavArrow} from 'components/Icon/IconNavArrow';
interface SidebarButtonProps {
title: string;
heading: boolean;
level: number;
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
isExpanded?: boolean;
isBreadcrumb?: boolean;
}
export function SidebarButton({
title,
heading,
level,
onClick,
isExpanded,
isBreadcrumb,
}: SidebarButtonProps) {
return (
<div
className={cn({
'my-1': heading || level === 1,
'my-3': level > 1,
})}>
<button
className={cn(
'p-2 pr-2 pl-5 w-full rounded-r-lg text-left hover:bg-gray-5 dark:hover:bg-gray-80 relative flex items-center justify-between',
{
'p-2 text-base': level > 1,
'text-link bg-highlight dark:bg-highlight-dark text-base font-bold hover:bg-highlight dark:hover:bg-highlight-dark hover:text-link dark:hover:text-link-dark':
!heading && isBreadcrumb && !isExpanded,
'p-4 my-6 text-2xl lg:my-auto lg:text-sm font-bold': heading,
'p-2 hover:text-gray-70 text-base font-bold text-primary dark:text-primary-dark':
!heading && !isBreadcrumb,
'text-primary dark:text-primary-dark': heading && !isBreadcrumb,
'text-primary dark:text-primary-dark text-base font-bold bg-card dark:bg-card-dark':
!heading && isExpanded,
}
)}
onClick={onClick}>
{title}
{typeof isExpanded && !heading && (
<span className="pr-2 text-gray-30">
<IconNavArrow displayDirection={isExpanded ? 'down' : 'right'} />
</span>
)}
</button>
</div>
);
}

84
beta/src/components/Layout/Sidebar/SidebarLink.tsx

@ -0,0 +1,84 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import * as React from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import cn from 'classnames';
import {IconNavArrow} from 'components/Icon/IconNavArrow';
import Link from 'next/link';
import {useIsMobile} from '../useMediaQuery';
interface SidebarLinkProps {
href: string;
selected?: boolean;
title: string;
level: number;
icon?: React.ReactNode;
heading?: boolean;
isExpanded?: boolean;
isBreadcrumb?: boolean;
hideArrow?: boolean;
}
export function SidebarLink({
href,
selected = false,
title,
level,
heading = false,
isExpanded,
isBreadcrumb,
hideArrow,
}: SidebarLinkProps) {
const ref = React.useRef<HTMLAnchorElement>(null);
const isMobile = useIsMobile();
React.useEffect(() => {
if (ref && ref.current && !!selected && !isMobile) {
scrollIntoView(ref.current, {
scrollMode: 'if-needed',
block: 'center',
inline: 'nearest',
});
}
}, [ref, selected, isMobile]);
return (
<Link href={href}>
<a
ref={ref}
title={title}
aria-current={selected ? 'page' : undefined}
className={cn(
'p-2 pr-2 w-full rounded-none lg:rounded-r-lg text-left hover:bg-gray-5 dark:hover:bg-gray-80 relative flex items-center justify-between',
{
'my-6': heading,
'text-primary dark:text-primary-dark': heading && !isBreadcrumb,
'text-sm pl-6': level > 0,
'pl-5': level < 2,
'text-base font-bold': level === 0,
'dark:text-primary-dark text-primary ': level === 0 && !selected,
'text-base text-link dark:text-link-dark': level === 1 && selected,
'dark:text-primary-dark text-primary': heading,
'text-base text-secondary dark:text-secondary-dark':
!selected && !heading,
'text-base text-link dark:text-link-dark bg-highlight dark:bg-highlight-dark border-blue-40 hover:bg-highlight hover:text-link dark:hover:bg-highlight-dark dark:hover:text-link-dark': selected,
}
)}>
{title}
{isExpanded != null && !heading && !hideArrow && (
<span
className={cn('pr-1', {
'text-link': isExpanded,
'text-gray-30': !isExpanded,
})}>
<IconNavArrow displayDirection={isExpanded ? 'down' : 'right'} />
</span>
)}
</a>
</Link>
);
}

155
beta/src/components/Layout/Sidebar/SidebarRouteTree.tsx

@ -0,0 +1,155 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {RouteItem} from 'components/Layout/useRouteMeta';
import {useRouter} from 'next/router';
import {removeFromLast} from 'utils/removeFromLast';
import {useRouteMeta} from '../useRouteMeta';
import {SidebarLink} from './SidebarLink';
import useCollapse from 'react-collapsed';
import {useLayoutEffect} from 'react';
interface SidebarRouteTreeProps {
isMobile?: boolean;
routeTree: RouteItem;
level?: number;
}
function CollapseWrapper({
isExpanded,
duration,
children,
}: {
isExpanded: boolean;
duration: number;
children: any;
}) {
const ref = React.useRef<HTMLDivElement | null>(null);
const timeoutRef = React.useRef<number | null>(null);
const {getCollapseProps} = useCollapse({
isExpanded,
duration,
});
// Disable pointer events while animating.
const isExpandedRef = React.useRef(isExpanded);
if (typeof window !== 'undefined') {
// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
const wasExpanded = isExpandedRef.current;
if (wasExpanded === isExpanded) {
return;
}
isExpandedRef.current = isExpanded;
if (ref.current !== null) {
const node: HTMLDivElement = ref.current;
node.style.pointerEvents = 'none';
if (timeoutRef.current !== null) {
window.clearTimeout(timeoutRef.current);
}
timeoutRef.current = window.setTimeout(() => {
node.style.pointerEvents = '';
}, duration + 100);
}
});
}
return (
<div
ref={ref}
style={{
opacity: isExpanded ? 1 : 0.5,
transition: `opacity ${duration}ms ease-in-out`,
animation: `nav-fadein ${duration}ms ease-in-out`,
}}>
<div {...getCollapseProps()}>{children}</div>
</div>
);
}
export function SidebarRouteTree({
isMobile,
routeTree,
level = 0,
}: SidebarRouteTreeProps) {
const {breadcrumbs} = useRouteMeta(routeTree);
const {pathname} = useRouter();
const slug = pathname;
const currentRoutes = routeTree.routes as RouteItem[];
const expandedPath = currentRoutes.reduce(
(acc: string | undefined, curr: RouteItem) => {
if (acc) return acc;
const breadcrumb = breadcrumbs.find((b) => b.path === curr.path);
if (breadcrumb) {
return curr.path;
}
if (curr.path === pathname) {
return pathname;
}
return undefined;
},
undefined
);
const expanded = expandedPath;
return (
<ul>
{currentRoutes.map(({path, title, routes, heading}) => {
const pagePath = path && removeFromLast(path, '.');
const selected = slug === pagePath;
// if current route item has no path and children treat it as an API sidebar heading
if (!path || !pagePath || heading) {
return (
<SidebarRouteTree
level={level + 1}
isMobile={isMobile}
routeTree={{title, routes}}
/>
);
}
// if route has a path and child routes, treat it as an expandable sidebar item
if (routes) {
const isExpanded = isMobile || expanded === path;
return (
<li key={`${title}-${path}-${level}-heading`}>
<SidebarLink
key={`${title}-${path}-${level}-link`}
href={pagePath}
selected={selected}
level={level}
title={title}
isExpanded={isExpanded}
isBreadcrumb={expandedPath === path}
hideArrow={isMobile}
/>
<CollapseWrapper duration={250} isExpanded={isExpanded}>
<SidebarRouteTree
isMobile={isMobile}
routeTree={{title, routes}}
level={level + 1}
/>
</CollapseWrapper>
</li>
);
}
// if route has a path and no child routes, treat it as a sidebar link
return (
<li key={`${title}-${path}-${level}-link`}>
<SidebarLink
href={pagePath}
selected={selected}
level={level}
title={title}
/>
</li>
);
})}
</ul>
);
}

8
beta/src/components/Layout/Sidebar/index.tsx

@ -0,0 +1,8 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
export {Sidebar} from './Sidebar';
export {SidebarButton} from './SidebarButton';
export {SidebarLink} from './SidebarLink';
export {SidebarRouteTree} from './SidebarRouteTree';

81
beta/src/components/Layout/Toc.tsx

@ -0,0 +1,81 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import cx from 'classnames';
import * as React from 'react';
import {useTocHighlight} from './useTocHighlight';
export function Toc({
headings,
}: {
headings: Array<{url: string; text: React.ReactNode; depth: number}>;
}) {
const {currentIndex} = useTocHighlight();
// TODO: We currently have a mismatch between the headings in the document
// and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges).
// Select the max TOC item we have here for now, but remove this after the fix.
const selectedIndex = Math.min(currentIndex, headings.length - 1);
return (
<nav
role="navigation"
className="pt-6 fixed top-0 right-0"
style={{
// This keeps the layout fixed width instead of adjusting for content.
width: 'inherit',
maxWidth: 'inherit',
}}>
<h2 className="mb-3 lg:mb-3 uppercase tracking-wide font-bold text-sm text-secondary dark:text-secondary-dark px-4 w-full">
On this page
</h2>
<div className="toc h-full overflow-y-auto pl-4">
<ul className="space-y-2 pb-16">
{headings &&
headings.length > 0 &&
headings.map((h, i) => {
if (h.url == null) {
// TODO: only log in DEV
console.error('Heading does not have URL');
}
return (
<li
key={`heading-${h.url}-${i}`}
className={cx(
'text-sm px-2 py-1 rounded-l-lg',
selectedIndex === i
? 'bg-highlight dark:bg-highlight-dark'
: null,
{
'pl-4': h?.depth === 3,
hidden: h.depth && h.depth > 3,
}
)}>
<a
className={cx(
selectedIndex === i
? 'text-link dark:text-link-dark font-bold'
: 'text-secondary dark:text-secondary-dark',
'block hover:text-link dark:hover:text-link-dark'
)}
href={h.url}>
{h.text}
</a>
</li>
);
})}
</ul>
</div>
<style jsx global>{`
.toc {
/** Screen - nav - toc offset */
max-height: calc(100vh - 7.5rem);
}
.toc-link > code {
overflow-wrap: break-word;
white-space: pre-wrap;
font-size: 90%;
}
`}</style>
</nav>
);
}

37
beta/src/components/Layout/useMediaQuery.tsx

@ -0,0 +1,37 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import {useState, useCallback, useEffect} from 'react';
const useMediaQuery = (width: number) => {
const [targetReached, setTargetReached] = useState(false);
const updateTarget = useCallback((e) => {
if (e.matches) {
setTargetReached(true);
} else {
setTargetReached(false);
}
}, []);
useEffect(() => {
const media = window.matchMedia(`(max-width: ${width}px)`);
media.addListener(updateTarget);
// Check on mount (callback is not called until a change occurs)
if (media.matches) {
setTargetReached(true);
}
return () => media.removeListener(updateTarget);
}, [updateTarget, width]);
return targetReached;
};
const useIsMobile = () => {
return useMediaQuery(640);
};
export {useMediaQuery, useIsMobile};

124
beta/src/components/Layout/useRouteMeta.tsx

@ -0,0 +1,124 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {useRouter} from 'next/router';
/**
* While Next.js provides file-based routing, we still need to construct
* a sidebar for navigation and provide each markdown page
* previous/next links and titles. To do this, we construct a nested
* route object that is infinitely nestable.
*/
export type RouteTag =
| 'foundation'
| 'intermediate'
| 'advanced'
| 'experimental'
| 'deprecated';
export interface RouteItem {
/** Page title (for the sidebar) */
title: string;
/** Optional page description for heading */
description?: string;
/* Additional meta info for page tagging */
tags?: RouteTag[];
/** Path to page */
path?: string;
/** Whether the entry is a heading */
heading?: boolean;
/** List of sub-routes */
routes?: RouteItem[];
}
export interface Routes {
/** List of routes */
routes: RouteItem[];
}
/** Routing metadata about a given route and it's siblings and parent */
export interface RouteMeta {
/** The previous route */
prevRoute?: RouteItem;
/** The next route */
nextRoute?: RouteItem;
/** The current route */
route?: RouteItem;
/** Trail of parent routes */
breadcrumbs?: RouteItem[];
}
export function useRouteMeta(rootRoute?: RouteItem) {
const sidebarContext = React.useContext(SidebarContext);
const routeTree = rootRoute || sidebarContext;
const router = useRouter();
const cleanedPath = router.pathname;
const breadcrumbs = getBreadcrumbs(cleanedPath, routeTree);
return {
...getRouteMeta(cleanedPath, routeTree),
breadcrumbs: breadcrumbs.length > 0 ? breadcrumbs : [routeTree],
};
}
export const SidebarContext = React.createContext<RouteItem>({title: 'root'});
// Performs a depth-first search to find the current route and its previous/next route
function getRouteMeta(
searchPath: string,
currentRoute: RouteItem,
ctx: RouteMeta = {}
): RouteMeta {
const {routes} = currentRoute;
if (ctx.route && !ctx.nextRoute) {
ctx.nextRoute = currentRoute;
}
if (currentRoute.path === searchPath) {
ctx.route = currentRoute;
}
if (!ctx.route) {
ctx.prevRoute = currentRoute;
}
if (!routes) {
return ctx;
}
for (const route of routes) {
getRouteMeta(searchPath, route, ctx);
}
return ctx;
}
// iterates the route tree from the current route to find its ancestors for breadcrumbs
function getBreadcrumbs(
path: string,
currentRoute: RouteItem,
breadcrumbs: RouteItem[] = []
): RouteItem[] {
if (currentRoute.path === path) {
return breadcrumbs;
}
if (!currentRoute.routes) {
return [];
}
for (const route of currentRoute.routes) {
const childRoute = getBreadcrumbs(path, route, [
...breadcrumbs,
currentRoute,
]);
if (childRoute?.length) {
return childRoute;
}
}
return [];
}

83
beta/src/components/Layout/useTocHighlight.tsx

@ -0,0 +1,83 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import React from 'react';
import {siteConfig} from 'siteConfig';
const TOP_OFFSET = 75;
export function getHeaderAnchors(): HTMLAnchorElement[] {
return Array.prototype.filter.call(
document.getElementsByClassName(siteConfig.headerIdConfig.className),
function (testElement) {
return (
testElement.parentNode.nodeName === 'H1' ||
testElement.parentNode.nodeName === 'H2' ||
testElement.parentNode.nodeName === 'H3'
);
}
);
}
/**
* Sets up Table of Contents highlighting.
*/
export function useTocHighlight() {
const [currentIndex, setCurrentIndex] = React.useState<number>(0);
const timeoutRef = React.useRef<number | null>(null);
React.useEffect(() => {
function updateActiveLink() {
const pageHeight = document.body.scrollHeight;
const scrollPosition = window.scrollY + window.innerHeight;
const headersAnchors = getHeaderAnchors();
if (scrollPosition >= 0 && pageHeight - scrollPosition <= TOP_OFFSET) {
// Scrolled to bottom of page.
setCurrentIndex(headersAnchors.length - 1);
return;
}
let index = -1;
while (index < headersAnchors.length - 1) {
const headerAnchor = headersAnchors[index + 1];
const {top} = headerAnchor.getBoundingClientRect();
if (top >= TOP_OFFSET) {
break;
}
index += 1;
}
setCurrentIndex(Math.max(index, 0));
}
function throttledUpdateActiveLink() {
if (timeoutRef.current === null) {
timeoutRef.current = window.setTimeout(() => {
timeoutRef.current = null;
updateActiveLink();
}, 100);
}
}
document.addEventListener('scroll', throttledUpdateActiveLink);
document.addEventListener('resize', throttledUpdateActiveLink);
updateActiveLink();
return () => {
if (timeoutRef.current != null) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
document.removeEventListener('scroll', throttledUpdateActiveLink);
document.removeEventListener('resize', throttledUpdateActiveLink);
};
}, []);
return {
currentIndex,
};
}

49
beta/src/components/Layout/useTwitter.tsx

@ -0,0 +1,49 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
/**
* Ported from gatsby-plugin-twitter
* https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-twitter
*/
import * as React from 'react';
const injectTwitterScript = function injectTwitterScript() {
function addJS(jsCode: any) {
const s = document.createElement('script');
s.type = 'text/javascript';
s.innerText = jsCode;
document.getElementsByTagName('head')[0].appendChild(s);
}
addJS(
'\n window.twttr = (function(d, s, id) {\n var js,\n fjs = d.getElementsByTagName(s)[0],\n t = window.twttr || {};\n if (d.getElementById(id)) return t;\n js = d.createElement(s);\n js.id = id;\n js.src = "https://platform.twitter.com/widgets.js";\n fjs.parentNode.insertBefore(js, fjs);\n t._e = [];\n t.ready = function(f) {\n t._e.push(f);\n };\n return t;\n })(document, "script", "twitter-wjs");\n '
);
};
let injectedTwitterScript = false;
const embedClasses = [
'.twitter-tweet',
'.twitter-timeline',
'.twitter-follow-button',
'.twitter-share-button',
].join(',');
export function useTwitter() {
React.useEffect(() => {
if (document.querySelector(embedClasses) !== null) {
if (!injectedTwitterScript) {
injectTwitterScript();
injectedTwitterScript = true;
}
if (
typeof (window as any).twttr !== 'undefined' &&
(window as any).twttr.widgets &&
typeof (window as any).twttr.widgets.load === 'function'
) {
(window as any).twttr.widgets.load();
}
}
}, []);
}

28
beta/src/components/Logo.tsx

@ -0,0 +1,28 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
export function Logo(props: JSX.IntrinsicElements['svg']) {
return (
<svg
width="100%"
height="100%"
viewBox="0 0 410 369"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}>
<path
d="M204.995 224.552C226.56 224.552 244.042 207.07 244.042 185.506C244.042 163.941 226.56 146.459 204.995 146.459C183.43 146.459 165.948 163.941 165.948 185.506C165.948 207.07 183.43 224.552 204.995 224.552Z"
fill="currentColor"
/>
<path
d="M409.99 184.505C409.99 153.707 381.437 126.667 335.996 108.925C343.342 60.6535 334.19 22.3878 307.492 6.98883C283.649 -6.77511 250.631 -0.0395641 214.512 25.9753C211.316 28.2692 208.143 30.7097 204.97 33.2477C201.822 30.7097 198.65 28.2692 195.477 25.9753C159.359 -0.0395641 126.34 -6.79951 102.497 6.98883C75.8237 22.3878 66.6721 60.6291 74.0422 108.852C28.5529 126.618 0 153.682 0 184.505C0 215.303 28.5528 242.342 73.9934 260.084C66.6477 308.356 75.7993 346.621 102.497 362.02C110.575 366.682 119.727 369 129.684 369C149.085 369 171.61 360.215 195.477 343.034C198.674 340.74 201.847 338.3 205.019 335.762C208.167 338.3 211.34 340.74 214.512 343.034C238.38 360.239 260.905 369 280.306 369C290.263 369 299.415 366.682 307.492 362.02C331.335 348.256 342 316.287 337.534 271.993C337.143 268.089 336.631 264.135 335.996 260.109C381.461 242.367 409.99 215.327 409.99 184.505ZM225.934 41.8136C246.238 27.1955 265.127 19.5814 280.306 19.5814C286.871 19.5814 292.728 20.9968 297.731 23.8765C315.204 33.9798 322.672 62.9475 317.327 102.433C299.756 97.0401 280.306 92.9158 259.392 90.2802C246.872 73.8074 233.597 58.9453 220.003 46.2551C221.98 44.7421 223.957 43.229 225.934 41.8136ZM112.259 23.8765C117.262 20.9968 123.119 19.5814 129.684 19.5814C144.863 19.5814 163.752 27.1711 184.056 41.8136C186.033 43.229 188.01 44.7176 189.986 46.2551C176.393 58.9453 163.142 73.783 150.622 90.2558C129.732 92.8914 110.258 97.0401 92.687 102.409C87.3424 62.9475 94.7857 33.9798 112.259 23.8765ZM19.5233 184.505C19.5233 164.322 40.9014 143.359 77.776 128.253C81.9003 146.141 88.0502 165.054 96.1768 184.456C88.0014 203.881 81.8515 222.819 77.7272 240.732C40.9014 225.626 19.5233 204.687 19.5233 184.505ZM184.056 327.196C154.966 348.134 128.805 354.675 112.259 345.133C94.7857 335.029 87.3181 306.062 92.6626 266.576C110.234 271.969 129.684 276.093 150.598 278.729C163.117 295.202 176.393 310.064 189.986 322.754C188.01 324.292 186.033 325.78 184.056 327.196ZM204.995 310.04C180.591 287.685 157.138 257.815 137.347 223.551C132.051 214.4 121.344 191.396 117 182.489C113.535 190.786 110.112 198.398 107.427 206.5C109.623 210.575 118.092 229.213 120.434 233.288C125.071 241.317 129.928 249.127 134.931 256.692C120.898 254.227 107.915 251.055 96.1035 247.321C102.815 217.011 116.213 182.064 137.347 145.458C142.545 136.453 153.838 116.346 159.5 108C150.568 109.147 143.395 108.767 135 110.5C132.56 114.453 122.777 131.645 120.434 135.721C115.749 143.823 111.454 151.925 107.427 159.978C102.546 146.581 98.8124 133.744 96.1524 121.64C125.755 112.293 162.727 106.411 204.995 106.411C215.562 106.411 237.63 106.197 247.49 106.905C242.048 99.7544 237.38 93.2819 231.694 86.888C227.082 86.7416 209.705 86.888 204.995 86.888C195.672 86.888 186.545 87.2053 177.589 87.7422C186.472 77.1752 195.672 67.5111 204.995 58.9697C229.375 81.3239 252.851 111.195 272.643 145.458C277.841 154.463 289.073 175.426 293.49 184.505C296.98 176.207 300.281 168.64 302.99 160.489C300.793 156.389 291.898 139.747 289.555 135.696C284.918 127.667 280.062 119.858 275.059 112.317C289.092 114.782 302.075 117.954 313.886 121.688C307.175 151.998 293.777 186.945 272.643 223.551C267.445 232.556 252.651 253.178 246.99 261.524C255.922 260.377 265.595 258.663 273.99 256.93C276.43 252.976 287.212 237.364 289.555 233.288C294.216 225.235 298.512 217.182 302.489 209.153C307.224 222.185 310.982 234.997 313.715 247.394C284.138 256.741 247.214 262.598 204.995 262.598C194.428 262.598 169.859 261.208 160 260.5C165.442 267.65 171.304 275.095 176.99 281.489C181.602 281.635 200.285 282.121 204.995 282.121C214.317 282.121 223.444 281.804 232.401 281.267C223.493 291.834 214.317 301.498 204.995 310.04ZM297.731 345.133C281.185 354.699 254.999 348.159 225.934 327.196C223.957 325.78 221.98 324.292 220.003 322.754C233.597 310.064 246.848 295.226 259.367 278.753C280.233 276.118 299.659 271.993 317.205 266.625C317.547 269.089 317.888 271.554 318.132 273.97C321.72 309.649 314.277 335.566 297.731 345.133ZM332.262 240.756C328.065 222.599 321.842 203.686 313.813 184.578C321.988 165.152 328.138 146.215 332.262 128.302C369.088 143.408 390.466 164.322 390.466 184.505C390.466 204.687 369.113 225.626 332.262 240.756Z"
fill="currentColor"
/>
</svg>
);
}
Logo.displayName = 'Logo';

127
beta/src/components/MDX/APIAnatomy.tsx

@ -0,0 +1,127 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import tailwindConfig from '../../../tailwind.config';
import CodeBlock from './CodeBlock';
interface APIAnatomyProps {
children: React.ReactNode;
}
interface AnatomyContent {
steps: React.ReactElement[];
code: React.ReactNode;
}
const twColors = tailwindConfig.theme.extend.colors;
const colors = [
{
hex: twColors['blue-40'],
border: 'border-blue-40',
background: 'bg-blue-40',
},
{
hex: twColors['yellow-40'],
border: 'border-yellow-40',
background: 'bg-yellow-40',
},
{
hex: twColors['green-50'],
border: 'border-green-50',
background: 'bg-green-50',
},
{
hex: twColors['purple-40'],
border: 'border-purple-40',
background: 'bg-purple-40',
},
];
export function APIAnatomy({children}: APIAnatomyProps) {
const [activeStep, setActiveStep] = React.useState<number | null>(null);
const ref = React.useRef<HTMLDivElement>();
const {steps, code} = React.Children.toArray(children).reduce(
(acc: AnatomyContent, child) => {
if (!React.isValidElement(child)) return acc;
switch (child.props.mdxType) {
case 'AnatomyStep':
acc.steps.push(
React.cloneElement(child, {
...child.props,
activeStep,
handleStepChange: setActiveStep,
stepNumber: acc.steps.length + 1,
})
);
break;
case 'pre':
acc.code = (
<CodeBlock
ref={ref}
{...child.props.children.props}
noMargin={true}
/>
);
break;
}
return acc;
},
{steps: [], code: null}
);
return (
<section className="my-8 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4">
<div className="lg:order-2">{code}</div>
<div className="lg:order-1 flex flex-col justify-center gap-y-2">
{steps}
</div>
</section>
);
}
interface AnatomyStepProps {
children: React.ReactNode;
title: string;
stepNumber: number;
activeStep?: number;
handleStepChange: (stepNumber: number) => void;
}
export function AnatomyStep({
title,
stepNumber,
children,
activeStep,
handleStepChange,
}: AnatomyStepProps) {
const color = colors[stepNumber - 1];
return (
<div
style={{
borderLeftColor: color.hex,
}}
className={cn(
'border-l-4 rounded-lg px-5 pt-8 pb-2 bg-opacity-5 shadow-inner',
color.border,
color.background
)}>
<div className="relative flex items-center justify-between">
<div
className={cn(
'inline align-middle text-center rounded-full w-5 h-5 absolute font-bold text-white text-code font-mono leading-tight -left-8',
color.background
)}>
{stepNumber}
</div>
<div className="text-primary dark:text-primary-dark leading-3 font-bold">
{title}
</div>
</div>
<div>{children}</div>
</div>
);
}

230
beta/src/components/MDX/Challenges/Challenges.tsx

@ -0,0 +1,230 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {Button} from 'components/Button';
import {H2} from 'components/MDX/Heading';
import {Navigation} from './Navigation';
import {IconHint} from '../../Icon/IconHint';
import {IconSolution} from '../../Icon/IconSolution';
import {IconArrowSmall} from '../../Icon/IconArrowSmall';
interface ChallengesProps {
children: React.ReactElement[];
isRecipes?: boolean;
}
export interface ChallengeContents {
id: string;
name: string;
order: number;
content: React.ReactNode;
solution: React.ReactNode;
hint?: React.ReactNode;
}
const parseChallengeContents = (
children: React.ReactElement[]
): ChallengeContents[] => {
const contents: ChallengeContents[] = [];
if (!children) {
return contents;
}
let challenge: Partial<ChallengeContents> = {};
let content: React.ReactElement[] = [];
React.Children.forEach(children, (child) => {
const {props} = child;
switch (props.mdxType) {
case 'Solution': {
challenge.solution = child;
challenge.content = content;
contents.push(challenge as ChallengeContents);
challenge = {};
content = [];
break;
}
case 'Hint': {
challenge.hint = child;
break;
}
case 'h3': {
challenge.order = contents.length + 1;
challenge.name = props.children;
challenge.id = props.id;
break;
}
default: {
content.push(child);
}
}
});
return contents;
};
export function Challenges({children, isRecipes}: ChallengesProps) {
const challenges = parseChallengeContents(children);
const scrollAnchorRef = React.useRef<HTMLDivElement>(null);
const [showHint, setShowHint] = React.useState(false);
const [showSolution, setShowSolution] = React.useState(false);
const [activeChallenge, setActiveChallenge] = React.useState(
challenges[0].id
);
const handleChallengeChange = (challengeId: string) => {
setShowHint(false);
setShowSolution(false);
setActiveChallenge(challengeId);
};
const toggleHint = () => {
if (showSolution && !showHint) {
setShowSolution(false);
}
setShowHint((hint) => !hint);
};
const toggleSolution = () => {
if (showHint && !showSolution) {
setShowHint(false);
}
setShowSolution((solution) => !solution);
};
const currentChallenge = challenges.find(({id}) => id === activeChallenge);
const nextChallenge = challenges.find(({order}) => {
if (!currentChallenge) {
return false;
}
return order === currentChallenge.order + 1;
});
return (
<div className="max-w-7xl mx-auto py-4 md:py-12">
<div
className={cn(
'border-gray-10 bg-card dark:bg-card-dark shadow-inner rounded-none -mx-5 sm:mx-auto sm:rounded-lg'
)}>
<div ref={scrollAnchorRef} className="py-2 px-5 sm:px-8 pb-0 md:pb-0">
<H2
id={isRecipes ? 'recipes' : 'challenges'}
className={cn(
'text-3xl mb-2 leading-10 relative',
isRecipes ? 'text-purple-50 dark:text-purple-30' : 'text-link'
)}>
{isRecipes ? 'Try out some recipes' : 'Try out some challenges'}
</H2>
<Navigation
activeChallenge={activeChallenge}
challenges={challenges}
handleChange={handleChallengeChange}
isRecipes={isRecipes}
/>
</div>
<div className="p-5 sm:py-8 sm:px-8">
<div key={activeChallenge}>
<h3 className="text-xl text-primary dark:text-primary-dark mb-2">
<div className="font-bold block md:inline">
{isRecipes ? 'Recipe' : 'Challenge'} {currentChallenge?.order}{' '}
of {challenges.length}
<span className="text-primary dark:text-primary-dark">: </span>
</div>
{currentChallenge?.name}
</h3>
<>{currentChallenge?.content}</>
</div>
<div className="flex justify-between items-center mt-4">
{currentChallenge?.hint ? (
<div>
<Button className="mr-2" onClick={toggleHint} active={showHint}>
<IconHint className="mr-1.5" />{' '}
{showHint ? 'Hide hint' : 'Show hint'}
</Button>
<Button
className="mr-2"
onClick={toggleSolution}
active={showSolution}>
<IconSolution className="mr-1.5" />{' '}
{showSolution ? 'Hide solution' : 'Show solution'}
</Button>
</div>
) : (
!isRecipes && (
<Button
className="mr-2"
onClick={toggleSolution}
active={showSolution}>
<IconSolution className="mr-1.5" />{' '}
{showSolution ? 'Hide solution' : 'Show solution'}
</Button>
)
)}
{nextChallenge && (
<Button
className={cn(
isRecipes
? 'bg-purple-50 border-purple-50 hover:bg-purple-50 focus:bg-purple-50 active:bg-purple-50'
: 'bg-link dark:bg-link-dark'
)}
onClick={() => {
setActiveChallenge(nextChallenge.id);
setShowSolution(false);
}}
active>
Next {isRecipes ? 'Recipe' : 'Challenge'}
<IconArrowSmall
displayDirection="right"
className="block ml-1.5"
/>
</Button>
)}
</div>
{showHint && currentChallenge?.hint}
{showSolution && (
<div className="mt-6">
<h3 className="text-2xl font-bold text-primary dark:text-primary-dark">
Solution
</h3>
{currentChallenge?.solution}
<div className="flex justify-between items-center mt-4">
<Button onClick={() => setShowSolution(false)}>
Close solution
</Button>
{nextChallenge && (
<Button
className={cn(
isRecipes ? 'bg-purple-50' : 'bg-link dark:bg-link-dark'
)}
onClick={() => {
setActiveChallenge(nextChallenge.id);
setShowSolution(false);
if (scrollAnchorRef.current) {
scrollAnchorRef.current.scrollIntoView({
block: 'start',
behavior: 'smooth',
});
}
}}
active>
Next Challenge
<IconArrowSmall
displayDirection="right"
className="block ml-1.5"
/>
</Button>
)}
</div>
</div>
)}
</div>
</div>
</div>
);
}

137
beta/src/components/MDX/Challenges/Navigation.tsx

@ -0,0 +1,137 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import React, {createRef} from 'react';
import cn from 'classnames';
import {IconChevron} from 'components/Icon/IconChevron';
import {ChallengeContents} from './Challenges';
const debounce = require('debounce');
export function Navigation({
challenges,
handleChange,
activeChallenge,
isRecipes,
}: {
challenges: ChallengeContents[];
handleChange: (id: string) => void;
activeChallenge: string;
isRecipes?: boolean;
}) {
const containerRef = React.useRef<HTMLDivElement>(null);
const challengesNavRef = React.useRef(
challenges.map(() => createRef<HTMLButtonElement>())
);
const [scrollPos, setScrollPos] = React.useState(0);
const canScrollLeft = scrollPos > 0;
const canScrollRight = scrollPos < challenges.length - 1;
const handleScrollRight = () => {
if (scrollPos < challenges.length - 1) {
const currentNavRef = challengesNavRef.current[scrollPos + 1].current;
if (!currentNavRef) {
return;
}
if (containerRef.current) {
containerRef.current.scrollLeft = currentNavRef.offsetLeft;
}
handleChange(challenges[scrollPos + 1].id);
setScrollPos(scrollPos + 1);
}
};
const handleScrollLeft = () => {
if (scrollPos > 0) {
const currentNavRef = challengesNavRef.current[scrollPos - 1].current;
if (!currentNavRef) {
return;
}
if (containerRef.current) {
containerRef.current.scrollLeft = currentNavRef.offsetLeft;
}
handleChange(challenges[scrollPos - 1].id);
setScrollPos(scrollPos - 1);
}
};
const handleSelectNav = (id: string) => {
const selectedChallenge = challenges.findIndex(
(challenge) => challenge.id === id
);
const currentNavRef = challengesNavRef.current[selectedChallenge].current;
if (containerRef.current) {
containerRef.current.scrollLeft = currentNavRef?.offsetLeft || 0;
}
handleChange(id);
setScrollPos(selectedChallenge);
};
const handleResize = React.useCallback(() => {
if (containerRef.current) {
const el = containerRef.current;
el.scrollLeft =
challengesNavRef.current[scrollPos].current?.offsetLeft || 0;
}
}, [containerRef, challengesNavRef, scrollPos]);
React.useEffect(() => {
handleResize();
window.addEventListener('resize', debounce(handleResize, 200));
return () => {
window.removeEventListener('resize', handleResize);
};
}, [handleResize]);
return (
<div className="flex items-center justify-between">
<div className="overflow-hidden">
<div
ref={containerRef}
className="flex relative transition-transform content-box overflow-x-auto">
{challenges.map(({name, id, order}, index) => (
<button
className={cn(
'py-2 mr-4 text-base border-b-4 duration-100 ease-in transition whitespace-nowrap overflow-ellipsis',
isRecipes &&
activeChallenge === id &&
'text-purple-50 border-purple-50 hover:text-purple-50 dark:text-purple-30 dark:border-purple-30 dark:hover:text-purple-30',
!isRecipes &&
activeChallenge === id &&
'text-link border-link hover:text-link dark:text-link-dark dark:border-link-dark dark:hover:text-link-dark'
)}
onClick={() => handleSelectNav(id)}
key={`button-${id}`}
ref={challengesNavRef.current[index]}>
{order}. {name}
</button>
))}
</div>
</div>
<div className="flex z-10 pb-2 pl-2">
<button
onClick={handleScrollLeft}
className={cn(
'bg-secondary-button dark:bg-secondary-button-dark h-8 px-2 rounded-l border-gray-20 border-r',
{
'text-primary dark:text-primary-dark': canScrollLeft,
'text-gray-30': !canScrollLeft,
}
)}>
<IconChevron displayDirection="left" />
</button>
<button
onClick={handleScrollRight}
className={cn(
'bg-secondary-button dark:bg-secondary-button-dark h-8 px-2 rounded-r-lg',
{
'text-primary dark:text-primary-dark': canScrollRight,
'text-gray-30': !canScrollRight,
}
)}>
<IconChevron displayDirection="right" />
</button>
</div>
</div>
);
}

14
beta/src/components/MDX/Challenges/index.tsx

@ -0,0 +1,14 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import React from 'react';
export {Challenges} from './Challenges';
export function Hint({children}: {children: React.ReactNode}) {
return <div>{children}</div>;
}
export function Solution({children}: {children: React.ReactNode}) {
return <div>{children}</div>;
}

7
beta/src/components/MDX/CodeBlock/CodeBlock.module.css

@ -0,0 +1,7 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
.codeViewer {
padding: 1.5rem 0.5rem;
}

179
beta/src/components/MDX/CodeBlock/CodeBlock.tsx

@ -0,0 +1,179 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {
ClasserProvider,
SandpackCodeViewer,
SandpackProvider,
SandpackThemeProvider,
} from '@codesandbox/sandpack-react';
import rangeParser from 'parse-numeric-range';
import {CodeBlockLightTheme} from '../Sandpack/Themes';
import styles from './CodeBlock.module.css';
interface InlineHiglight {
step: number;
line: number;
startColumn: number;
endColumn: number;
}
const CodeBlock = React.forwardRef(
(
{
children,
className = 'language-js',
metastring,
noMargin,
noMarkers,
}: {
children: string;
className?: string;
metastring: string;
noMargin?: boolean;
noMarkers?: boolean;
},
ref?: React.Ref<HTMLDivElement>
) => {
const getDecoratedLineInfo = () => {
if (!metastring) {
return [];
}
const linesToHighlight = getHighlightLines(metastring);
const highlightedLineConfig = linesToHighlight.map((line) => {
return {
className: 'bg-github-highlight',
line,
};
});
const inlineHighlightLines = getInlineHighlights(metastring, children);
const inlineHighlightConfig = inlineHighlightLines.map(
(line: InlineHiglight) => ({
...line,
elementAttributes: {'data-step': `${line.step}`},
className: cn(
'code-step bg-opacity-10 relative rounded-md p-1 ml-2',
{
'pl-3 before:content-[attr(data-step)] before:block before:w-4 before:h-4 before:absolute before:top-1 before:-left-2 before:rounded-full before:text-white before:text-center before:text-xs before:leading-4': !noMarkers,
'bg-blue-40 before:bg-blue-40': line.step === 1,
'bg-yellow-40 before:bg-yellow-40': line.step === 2,
'bg-green-40 before:bg-green-40': line.step === 3,
'bg-purple-40 before:bg-purple-40': line.step === 4,
}
),
})
);
return highlightedLineConfig.concat(inlineHighlightConfig);
};
// e.g. "language-js"
const language = className.substring(9);
const filename = '/index.' + language;
const decorators = getDecoratedLineInfo();
return (
<div
className={cn(
'rounded-lg h-full w-full overflow-x-auto flex items-center bg-white shadow-lg',
!noMargin && 'my-8'
)}>
<SandpackProvider
customSetup={{
entry: filename,
files: {
[filename]: {
code: children.trimEnd(),
},
},
}}>
<SandpackThemeProvider theme={CodeBlockLightTheme}>
<ClasserProvider
classes={{
'sp-cm': styles.codeViewer,
}}>
<SandpackCodeViewer
ref={ref}
showLineNumbers={false}
decorators={decorators}
/>
</ClasserProvider>
</SandpackThemeProvider>
</SandpackProvider>
</div>
);
}
);
export default CodeBlock;
/**
*
* @param metastring string provided after the language in a markdown block
* @returns array of lines to highlight
* @example
* ```js {1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active
* ...
* ```
*
* -> The metastring is `{1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active`
*/
function getHighlightLines(metastring: string): number[] {
const HIGHLIGHT_REGEX = /{([\d,-]+)}/;
const parsedMetastring = HIGHLIGHT_REGEX.exec(metastring);
if (!parsedMetastring) {
return [];
}
return rangeParser(parsedMetastring[1]);
}
/**
*
* @param metastring string provided after the language in a markdown block
* @returns InlineHighlight[]
* @example
* ```js {1-3,7} [[1, 1, 'count'], [2, 4, 'setCount']] App.js active
* ...
* ```
*
* -> The metastring is `{1-3,7} [[1, 1, 'count', [2, 4, 'setCount']] App.js active`
*/
function getInlineHighlights(metastring: string, code: string) {
const INLINE_HIGHT_REGEX = /(\[\[.*\]\])/;
const parsedMetastring = INLINE_HIGHT_REGEX.exec(metastring);
if (!parsedMetastring) {
return [];
}
const lines = code.split('\n');
const encodedHiglights = JSON.parse(parsedMetastring[1]);
return encodedHiglights.map(([step, lineNo, substr, fromIndex]: any[]) => {
const line = lines[lineNo - 1];
let index = line.indexOf(substr);
const lastIndex = line.lastIndexOf(substr);
if (index !== lastIndex) {
if (fromIndex === undefined) {
throw Error(
"Found '" +
substr +
"' twice. Specify fromIndex as the fourth value in the tuple."
);
}
index = line.indexOf(substr, fromIndex);
}
if (index === -1) {
throw Error("Could not find: '" + substr + "'");
}
return {
step,
line: lineNo,
startColumn: index,
endColumn: index + substr.length,
};
});
}

7
beta/src/components/MDX/CodeBlock/index.tsx

@ -0,0 +1,7 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import CodeBlock from './CodeBlock';
export default CodeBlock;

46
beta/src/components/MDX/CodeDiagram.tsx

@ -0,0 +1,46 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import CodeBlock from './CodeBlock';
interface CodeDiagramProps {
children: React.ReactNode;
flip?: boolean;
}
export function CodeDiagram({children, flip = false}: CodeDiagramProps) {
const illustration = React.Children.toArray(children).filter((child: any) => {
return child.props?.mdxType === 'img';
});
const content = React.Children.toArray(children).map((child: any) => {
if (child.props?.mdxType === 'pre') {
return (
<CodeBlock
{...child.props.children.props}
noMargin={true}
noMarkers={true}
/>
);
} else if (child.props?.mdxType === 'img') {
return null;
} else {
return child;
}
});
if (flip) {
return (
<section className="my-8 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4">
{illustration}
<div className="flex flex-col justify-center">{content}</div>
</section>
);
}
return (
<section className="my-8 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-4">
<div className="flex flex-col justify-center">{content}</div>
<div className="py-4">{illustration}</div>
</section>
);
}

82
beta/src/components/MDX/ConsoleBlock.tsx

@ -0,0 +1,82 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {IconWarning} from '../Icon/IconWarning';
import {IconError} from '../Icon/IconError';
type LogLevel = 'info' | 'warning' | 'error';
interface ConsoleBlockProps {
level?: LogLevel;
children: React.ReactNode;
}
const Box = ({
width = '60px',
height = '17px',
className,
customStyles,
}: {
width?: string;
height?: string;
className?: string;
customStyles?: Record<string, string>;
}) => (
<div
className={className}
style={{width, height, backgroundColor: '#C4C4C4', ...customStyles}}></div>
);
Box.displayName = 'Box';
function ConsoleBlock({level = 'info', children}: ConsoleBlockProps) {
let message: string | undefined;
if (typeof children === 'string') {
message = children;
} else if (
React.isValidElement(children) &&
typeof children.props.children === 'string'
) {
message = children.props.children;
}
return (
<div className="mb-4">
<div
className="flex w-full rounded-t-lg"
style={{backgroundColor: '#DADEE0'}}>
<div className="px-4 py-2" style={{borderRight: '1px solid #C4C4C4'}}>
<Box width="15px" />
</div>
<div className="flex text-sm px-4">
<div className="border-b-2" style={{borderColor: '#C4C4C4'}}>
Console
</div>
<div className="px-4 py-2 flex">
<Box className="mr-2" />
<Box className="mr-2 hidden md:block" />
<Box className="hidden md:block" />
</div>
</div>
</div>
<div
className={cn(
'flex px-4 pt-4 pb-6 items-center content-center font-mono text-code rounded-b-md',
{
'bg-red-30 text-red-40 bg-opacity-25': level === 'error',
'bg-yellow-5 text-yellow-50': level === 'warning',
'bg-gray-5 text-secondary dark:text-secondary-dark':
level === 'info',
}
)}>
{level === 'error' && <IconError className="self-start mt-1.5" />}
{level === 'warning' && <IconWarning className="self-start mt-1" />}
<div className="px-3">{message}</div>
</div>
</div>
);
}
export default ConsoleBlock;

25
beta/src/components/MDX/Convention.tsx

@ -0,0 +1,25 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {H3} from './Heading';
interface ConventionProps {
children: React.ReactNode;
name: string;
}
function Convention({children, name}: ConventionProps) {
const id = name ? `${name}-conventions` : 'conventions';
return (
<section className="my-12">
<H3 id={id} className="text-2xl leading-9">
Conventions
</H3>
{children}
</section>
);
}
export default Convention;

66
beta/src/components/MDX/ExpandableCallout.tsx

@ -0,0 +1,66 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {IconNote} from '../Icon/IconNote';
import {IconGotcha} from '../Icon/IconGotcha';
type CalloutVariants = 'gotcha' | 'note';
interface ExpandableCalloutProps {
children: React.ReactNode;
type: CalloutVariants;
}
const variantMap = {
note: {
title: 'Note',
Icon: IconNote,
containerClasses:
'bg-green-5 dark:bg-green-60 dark:bg-opacity-20 text-primary dark:text-primary-dark text-lg',
textColor: 'text-green-60 dark:text-green-40',
overlayGradient:
'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)',
},
gotcha: {
title: 'Gotcha',
Icon: IconGotcha,
containerClasses: 'bg-yellow-5 dark:bg-yellow-60 dark:bg-opacity-20',
textColor: 'text-yellow-50 dark:text-yellow-40',
overlayGradient:
'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)',
},
};
function ExpandableCallout({children, type}: ExpandableCalloutProps) {
const contentRef = React.useRef<HTMLDivElement>(null);
const variant = variantMap[type];
return (
<div
className={cn(
'pt-8 pb-4 px-5 sm:px-8 my-8 relative rounded-none shadow-inner -mx-5 sm:mx-auto sm:rounded-lg',
variant.containerClasses
)}>
<h3 className={cn('mb-2 text-2xl font-bold', variant.textColor)}>
<variant.Icon
className={cn('inline mr-3 mb-1 text-lg', variant.textColor)}
/>
{variant.title}
</h3>
<div className="relative">
<div ref={contentRef} className="py-2">
{children}
</div>
</div>
</div>
);
}
ExpandableCallout.defaultProps = {
type: 'note',
};
export default ExpandableCallout;

85
beta/src/components/MDX/ExpandableExample.tsx

@ -0,0 +1,85 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {IconChevron} from '../Icon/IconChevron';
import {IconDeepDive} from '../Icon/IconDeepDive';
import {IconCodeBlock} from '../Icon/IconCodeBlock';
import {Button} from '../Button';
interface ExpandableExampleProps {
children: React.ReactNode;
title: string;
excerpt?: string;
type: 'DeepDive' | 'Example';
}
function ExpandableExample({
children,
title,
excerpt,
type,
}: ExpandableExampleProps) {
const [isExpanded, setIsExpanded] = React.useState(false);
const isDeepDive = type === 'DeepDive';
const isExample = type === 'Example';
return (
<div
className={cn('my-12 rounded-lg shadow-inner relative', {
'dark:bg-opacity-20 dark:bg-purple-60 bg-purple-5': isDeepDive,
'dark:bg-opacity-20 dark:bg-yellow-60 bg-yellow-5': isExample,
})}>
<div className="p-8">
<h5
className={cn('mb-4 uppercase font-bold flex items-center text-sm', {
'dark:text-purple-30 text-purple-50': isDeepDive,
'dark:text-yellow-30 text-yellow-60': isExample,
})}>
{isDeepDive && (
<>
<IconDeepDive className="inline mr-2 dark:text-purple-30 text-purple-40" />
Deep Dive
</>
)}
{isExample && (
<>
<IconCodeBlock className="inline mr-2 dark:text-yellow-30 text-yellow-50" />
Example
</>
)}
</h5>
<div className="mb-4">
<h3 className="text-xl font-bold text-primary dark:text-primary-dark">
{title}
</h3>
{excerpt && <div>{excerpt}</div>}
</div>
<Button
active={true}
className={cn({
'bg-purple-50 border-purple-50 hover:bg-purple-40 focus:bg-purple-50 active:bg-purple-50': isDeepDive,
'bg-yellow-50 border-yellow-50 hover:bg-yellow-40 focus:bg-yellow-50 active:bg-yellow-50': isExample,
})}
onClick={() => setIsExpanded((current) => !current)}>
<span className="mr-1">
<IconChevron displayDirection={isExpanded ? 'up' : 'down'} />
</span>
{isExpanded ? 'Hide Details' : 'Show Details'}
</Button>
</div>
<div
className={cn('p-8 border-t', {
'dark:border-purple-60 border-purple-10 ': isDeepDive,
'dark:border-yellow-60 border-yellow-50': isExample,
hidden: !isExpanded,
})}>
{children}
</div>
</div>
);
}
export default ExpandableExample;

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save