diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..a8b9b8bb --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ['react', 'es2015', 'stage-1'], + "plugins": ['add-module-exports'] +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..ff8a5577 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +node_modules/* + +# Ignore markdown files and examples +content/* + +# Ignore built files +public/* \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..78354d1e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,7 @@ +{ + "plugins": [ + "prettier", + "react" + ], + "parser": "babel-eslint", +} \ No newline at end of file diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 00000000..cb19818a --- /dev/null +++ b/.flowconfig @@ -0,0 +1,33 @@ +[ignore] + +/content/.* +/node_modules/.* +/public/.* + +[include] + +[libs] +./node_modules/fbjs/flow/lib/dev.js +./flow + +[options] +module.system=haste + +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable +unsafe.enable_getters_and_setters=true + +munge_underscores=false + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe +suppress_type=$FlowExpectedError + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*www[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*www[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError + +[version] +^0.56.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..237a1c46 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.cache +.DS_STORE +node_modules +public \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..c9dc0490 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +8.4 diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..d21a91a9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,393 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public licenses. +Notwithstanding, Creative Commons may elect to apply one of its public +licenses to material it publishes and in those instances will be +considered the "Licensor." Except for the limited purpose of indicating +that material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the public +licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2bff0510 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# reactjs.org + +## Getting started + +### Prerequisites + +1. Git +1. Node: install version 8.4 or greater +1. Yarn: `npm i -g yarn` to install it globally via NPM +1. A clone of the [reactjs.org repo](https://github.com/facebook/reactjs.org) on your local machine +1. A fork of the repo (for any contributions) + +### 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 hot-reloading development server (powered by [Gatsby](https://www.gatsbyjs.org)) +1. `open http://localhost:8000` to open the site in your favorite browser + +## Contributing + +### Create a branch + +1. `git checkout master` from any folder in your local react repository +1. `git pull origin master` 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" 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 `Fixed 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/facebook/reactjs.org) and you should see recently pushed branches. +1. Follow GitHub's instructions. +1. If possible include screenshots of visual changes. A Netlify build will also be automatically created once you make your PR so other people can see your change. + +## Troubleshooting + +- `yarn reset` to clear the local cache \ No newline at end of file diff --git a/flow-typed/hex2rgba.js b/flow-typed/hex2rgba.js new file mode 100644 index 00000000..75c0eb96 --- /dev/null +++ b/flow-typed/hex2rgba.js @@ -0,0 +1,3 @@ +declare module 'hex2rgba' { + declare module.exports: (hex : string, alpha? : number) => string; +} diff --git a/gatsby-config.js b/gatsby-config.js new file mode 100644 index 00000000..b1469a55 --- /dev/null +++ b/gatsby-config.js @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +module.exports = { + siteMetadata: { + title: 'React: A JavaScript library for building user interfaces', + siteUrl: 'https://reactjs.org', + rssFeedTitle: 'React', + rssFeedDescription: 'A JavaScript library for building user interfaces', + }, + mapping: { + 'MarkdownRemark.frontmatter.author': 'AuthorYaml', + }, + plugins: [ + 'gatsby-source-react-error-codes', + 'gatsby-transformer-authors-yaml', + 'gatsby-plugin-netlify', + 'gatsby-plugin-glamor', + 'gatsby-plugin-react-next', + 'gatsby-plugin-twitter', + { + resolve: 'gatsby-plugin-nprogress', + options: { + color: '#61dafb', + }, + }, + { + resolve: 'gatsby-source-filesystem', + options: { + path: `${__dirname}/src/pages`, + name: 'pages', + }, + }, + { + resolve: 'gatsby-source-filesystem', + options: { + name: 'packages', + path: `${__dirname}/content/`, + }, + }, + { + resolve: 'gatsby-transformer-remark', + options: { + plugins: [ + 'gatsby-remark-responsive-iframe', + { + resolve: 'gatsby-remark-images', + options: { + maxWidth: 840, + }, + }, + 'gatsby-remark-autolink-headers', + 'gatsby-remark-use-jsx', + { + resolve: 'gatsby-remark-prismjs', + options: { + classPrefix: 'gatsby-code-', + }, + }, + 'gatsby-remark-copy-linked-files', + 'gatsby-remark-smartypants', + ], + }, + }, + 'gatsby-transformer-sharp', + 'gatsby-plugin-sharp', + { + resolve: 'gatsby-plugin-google-analytics', + options: { + trackingId: 'UA-41298772-1', + }, + }, + { + resolve: 'gatsby-plugin-feed', + options: { + query: ` + { + site { + siteMetadata { + title: rssFeedTitle + description: rssFeedDescription + siteUrl + site_url: siteUrl + } + } + }`, + feeds: [ + { + serialize: ({query: {site, allMarkdownRemark}}) => { + return allMarkdownRemark.edges.map(edge => { + return Object.assign( + {}, + { + title: edge.node.frontmatter.title, + description: edge.node.html, + date: require('moment')(edge.node.fields.date).format( + 'MMMM DD, YYYY, h:mm A', + ), + url: site.siteMetadata.siteUrl + edge.node.fields.slug, + guid: site.siteMetadata.siteUrl + edge.node.fields.slug, + }, + ); + }); + }, + query: ` + { + allMarkdownRemark + (limit: 10, + filter: {id: {regex: "/blog/"}}, + sort: {fields: [fields___date], + order: DESC}) { + edges { + node { + fields { + date + slug + } + frontmatter { + title + } + html + } + } + } + } + `, + output: '/feed.xml', + }, + ], + }, + }, + 'gatsby-plugin-react-helmet', + ], +}; diff --git a/gatsby-node.js b/gatsby-node.js new file mode 100644 index 00000000..1bac337b --- /dev/null +++ b/gatsby-node.js @@ -0,0 +1,244 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +const {resolve} = require('path'); +const webpack = require('webpack'); + +exports.modifyWebpackConfig = ({config, stage}) => { + // See https://github.com/FormidableLabs/react-live/issues/5 + config.plugin('ignore', () => new webpack.IgnorePlugin(/^(xor|props)$/)); + + config.merge({ + resolve: { + root: resolve(__dirname, './src'), + extensions: ['', '.js', '.jsx', '.json'], + }, + }); + return config; +}; + +exports.createPages = async ({graphql, boundActionCreators}) => { + const {createPage, createRedirect} = boundActionCreators; + + const blogTemplate = resolve('./src/templates/blog.js'); + const communityTemplate = resolve('./src/templates/community.js'); + const docsTemplate = resolve('./src/templates/docs.js'); + const tutorialTemplate = resolve('./src/templates/tutorial.js'); + const homeTemplate = resolve('./src/templates/home.js'); + + const allMarkdown = await graphql( + ` + { + allMarkdownRemark(limit: 1000) { + edges { + node { + fields { + redirect + slug + } + } + } + } + } + `, + ); + + if (allMarkdown.errors) { + console.error(allMarkdown.errors); + + throw Error(allMarkdown.errors); + } + + allMarkdown.data.allMarkdownRemark.edges.forEach(edge => { + const slug = edge.node.fields.slug; + + if (slug === '/index.html') { + createPage({ + path: '/', + component: homeTemplate, + context: { + slug, + }, + }); + } else if (slug === 'docs/error-decoder.html') { + // No-op so far as markdown templates go. + // Error codes are managed by a page in src/pages + // (which gets created by Gatsby during a separate phase). + } else if ( + slug.includes('blog/') || + slug.includes('community/') || + slug.includes('contributing/') || + slug.includes('docs/') || + slug.includes('tutorial/') || + slug.includes('warnings/') + ) { + let template; + if (slug.includes('blog/')) { + template = blogTemplate; + } else if (slug.includes('community/')) { + template = communityTemplate; + } else if ( + slug.includes('contributing/') || + slug.includes('docs/') || + slug.includes('warnings/') + ) { + template = docsTemplate; + } else if (slug.includes('tutorial/')) { + template = tutorialTemplate; + } + + const createArticlePage = path => + createPage({ + path: path, + component: template, + context: { + slug, + }, + }); + + // Register primary URL. + createArticlePage(slug); + + // Register redirects as well if the markdown specifies them. + if (edge.node.fields.redirect) { + let redirect = JSON.parse(edge.node.fields.redirect); + if (!Array.isArray(redirect)) { + redirect = [redirect]; + } + + redirect.forEach(fromPath => + createRedirect({ + fromPath: `/${fromPath}`, + redirectInBrowser: true, + toPath: `/${slug}`, + }), + ); + } + } + }); + + const newestBlogEntry = await graphql( + ` + { + allMarkdownRemark( + limit: 1, + filter: { id: { regex: "/blog/" } } + sort: { fields: [fields___date], order: DESC } + ) { + edges { + node { + fields { + slug + } + } + } + } + } + `, + ); + const newestBlogNode = newestBlogEntry.data.allMarkdownRemark.edges[0].node; + + // Blog landing page should always show the most recent blog entry. + createRedirect({ + fromPath: '/blog/', + redirectInBrowser: true, + toPath: newestBlogNode.fields.slug, + }); +}; + +// Parse date information out of blog post filename. +const BLOG_POST_FILENAME_REGEX = /([0-9]+)\-([0-9]+)\-([0-9]+)\-(.+)\.md$/; + +// Add custom fields to MarkdownRemark nodes. +exports.onCreateNode = ({node, boundActionCreators, getNode}) => { + const {createNodeField} = boundActionCreators; + + switch (node.internal.type) { + case 'MarkdownRemark': + const {permalink, redirect_from} = node.frontmatter; + const {relativePath} = getNode(node.parent); + + let slug = permalink; + + if (!slug) { + if (relativePath.includes('blog')) { + // Blog posts don't have embedded permalinks. + // Their slugs follow a pattern: /blog////.html + // The date portion comes from the file name: -.md + const match = BLOG_POST_FILENAME_REGEX.exec(relativePath); + const year = match[1]; + const month = match[2]; + const day = match[3]; + const filename = match[4]; + + slug = `/blog/${year}/${month}/${day}/${filename}.html`; + + const date = new Date(year, month - 1, day); + + // Blog posts are sorted by date and display the date in their header. + createNodeField({ + node, + name: 'date', + value: date.toJSON(), + }); + } + } + + if (!slug) { + slug = `/${relativePath.replace('.md', '.html')}`; + + // This should (probably) only happen for the index.md, + // But let's log it in case it happens for other files also. + console.warn( + `Warning: No slug found for "${relativePath}". Falling back to default "${slug}".`, + ); + } + + // Used to generate URL to view this content. + createNodeField({ + node, + name: 'slug', + value: slug, + }); + + // Used to generate a GitHub edit link. + createNodeField({ + node, + name: 'path', + value: relativePath, + }); + + // Used by createPages() above to register redirects. + createNodeField({ + node, + name: 'redirect', + value: redirect_from ? JSON.stringify(redirect_from) : '', + }); + return; + } +}; + +exports.onCreatePage = async ({page, boundActionCreators}) => { + const {createPage} = boundActionCreators; + + return new Promise(resolvePromise => { + // page.matchPath is a special key that's used for matching pages only on the client. + // Explicitly wire up all error code wildcard matches to redirect to the error code page. + if (page.path.includes('docs/error-decoder.html')) { + page.matchPath = 'docs/error-decoder:path?'; + page.context.slug = 'docs/error-decoder.html'; + + createPage(page); + } + + resolvePromise(); + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 00000000..36ff14e1 --- /dev/null +++ b/package.json @@ -0,0 +1,80 @@ +{ + "name": "react-website", + "description": "React website", + "version": "0.0.1", + "bugs": { + "url": "https://github.com/facebook/react" + }, + "resolutions": { + "gatsby/graphql": "0.10.5", + "gatsby/react": "16.0.0", + "gatsby/react-dom": "16.0.0" + }, + "dependencies": { + "@gaearon/react-live": "1.7.1-with-safari-fix", + "array-from": "^2.1.1", + "babel-eslint": "^8.0.1", + "eslint": "^4.8.0", + "eslint-config-react": "^1.1.7", + "eslint-plugin-flowtype": "^2.37.0", + "eslint-plugin-prettier": "^2.3.1", + "eslint-plugin-react": "^7.4.0", + "flow-bin": "^0.56.0", + "gatsby": "^1.9.9", + "gatsby-link": "^1.6.9", + "gatsby-plugin-feed": "^1.3.9", + "gatsby-plugin-glamor": "^1.6.4", + "gatsby-plugin-google-analytics": "^1.0.4", + "gatsby-plugin-manifest": "^1.0.4", + "gatsby-plugin-netlify": "^1.0.4", + "gatsby-plugin-nprogress": "^1.0.7", + "gatsby-plugin-react-helmet": "^1.0.3", + "gatsby-plugin-react-next": "^1.0.3", + "gatsby-plugin-sharp": "^1.6.2", + "gatsby-plugin-twitter": "^1.0.10", + "gatsby-remark-autolink-headers": "^1.4.4", + "gatsby-remark-copy-linked-files": "^1.5.2", + "gatsby-remark-images": "^1.5.11", + "gatsby-remark-prismjs": "^1.2.1", + "gatsby-remark-responsive-iframe": "^1.4.3", + "gatsby-remark-smartypants": "^1.4.3", + "gatsby-source-filesystem": "^1.4.4", + "gatsby-transformer-remark": "^1.7.2", + "gatsby-transformer-sharp": "^1.6.1", + "glamor": "^2.20.40", + "hex2rgba": "^0.0.1", + "prettier": "^1.7.4", + "remarkable": "^1.7.1", + "request-promise": "^4.2.2", + "rimraf": "^2.6.1", + "slugify": "^1.2.1", + "string.prototype.includes": "^1.0.0", + "string.prototype.repeat": "^0.2.0", + "unist-util-visit": "^1.1.3" + }, + "engines": { + "node": ">=8.4.0" + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "gatsby" + ], + "license": "MIT", + "main": "n/a", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "build": "gatsby build", + "check-all": "yarn prettier && yarn lint && yarn flow", + "dev": "gatsby develop -H 0.0.0.0", + "lint": "eslint .", + "netlify": "yarn install && yarn build", + "prettier": "prettier --no-bracket-spacing --single-quote --jsx-bracket-same-line --trailing-comma all --print-width 80 --parser flow --write 'src/**/*.js'", + "reset": "rimraf ./.cache" + }, + "devDependencies": { + "eslint-config-prettier": "^2.6.0" + } +} diff --git a/plugins/gatsby-remark-use-jsx/index.js b/plugins/gatsby-remark-use-jsx/index.js new file mode 100644 index 00000000..c76a2276 --- /dev/null +++ b/plugins/gatsby-remark-use-jsx/index.js @@ -0,0 +1,22 @@ +const visit = require('unist-util-visit'); + +// Always treat JS blocks as JSX. +// TODO: maybe we can just change it in Markdown in the future? +module.exports = ({markdownAST}) => { + visit(markdownAST, `code`, node => { + if (typeof node.lang !== 'string') { + return; + } + if (node.lang.indexOf('jsx') === 0) { + // Already JSX (with optional line range). + return; + } + // Turn JS into JSX, preserving the optional line range. + if (node.lang.indexOf('js') === 0) { + node.lang = 'jsx' + node.lang.substring('js'.length); + } + if (node.lang.indexOf('javascript') === 0) { + node.lang = 'jsx' + node.lang.substring('javascript'.length); + } + }); +}; diff --git a/plugins/gatsby-remark-use-jsx/package.json b/plugins/gatsby-remark-use-jsx/package.json new file mode 100644 index 00000000..dd1e65dc --- /dev/null +++ b/plugins/gatsby-remark-use-jsx/package.json @@ -0,0 +1,4 @@ +{ + "name": "gatsby-remark-use-jsx", + "version": "0.0.1" +} \ No newline at end of file diff --git a/plugins/gatsby-source-react-error-codes/gatsby-node.js b/plugins/gatsby-source-react-error-codes/gatsby-node.js new file mode 100644 index 00000000..18dd5be7 --- /dev/null +++ b/plugins/gatsby-source-react-error-codes/gatsby-node.js @@ -0,0 +1,19 @@ +const request = require('request-promise'); + +const errorCodesUrl = 'http://raw.githubusercontent.com/facebook/react/master/scripts/error-codes/codes.json'; + +exports.sourceNodes = async ({ boundActionCreators }) => { + const { createNode } = boundActionCreators; + + const jsonString = await request(errorCodesUrl); + + createNode({ + id: 'error-codes', + children: [], + parent: 'ERRORS', + internal: { + type: 'ErrorCodesJson', + contentDigest: jsonString, + }, + }); +}; \ No newline at end of file diff --git a/plugins/gatsby-source-react-error-codes/package.json b/plugins/gatsby-source-react-error-codes/package.json new file mode 100644 index 00000000..7993c3cb --- /dev/null +++ b/plugins/gatsby-source-react-error-codes/package.json @@ -0,0 +1,4 @@ +{ + "name": "gatsby-source-react-error-codes", + "version": "0.0.1" +} \ No newline at end of file diff --git a/plugins/gatsby-transformer-authors-yaml/gatsby-node.js b/plugins/gatsby-transformer-authors-yaml/gatsby-node.js new file mode 100644 index 00000000..fb2d2553 --- /dev/null +++ b/plugins/gatsby-transformer-authors-yaml/gatsby-node.js @@ -0,0 +1,33 @@ +const readFileSync = require('fs').readFileSync; +const resolve = require('path').resolve; +const safeLoad = require('js-yaml').safeLoad; + +// TODO It would be nice to replace this plugin with gatsby-transformer-yaml +// That plugin errors on some of the YML files in the docs folder though, +// And doesn't currently support any options to whitelist/blacklist files. + +// Reads authors.yml data into GraphQL. +// This is auto-linked by gatsby-config.js to blog posts. +exports.sourceNodes = ({graphql, boundActionCreators}) => { + const {createNode} = boundActionCreators; + + const path = resolve(__dirname, '../../content/authors.yml'); + const file = readFileSync(path, 'utf8'); + const authors = safeLoad(file); + + // authors.yml structure is {[username: string]: {name: string, url: string}} + Object.keys(authors).forEach(username => { + const author = authors[username]; + + createNode({ + id: username, + children: [], + parent: 'AUTHORS', + internal: { + type: 'AuthorYaml', + contentDigest: JSON.stringify(author), + }, + frontmatter: author, + }); + }); +}; diff --git a/plugins/gatsby-transformer-authors-yaml/package.json b/plugins/gatsby-transformer-authors-yaml/package.json new file mode 100644 index 00000000..71e51554 --- /dev/null +++ b/plugins/gatsby-transformer-authors-yaml/package.json @@ -0,0 +1,4 @@ +{ + "name": "gatsby-transformer-authors-yaml", + "version": "0.0.1" +} \ No newline at end of file diff --git a/src/components/CodeEditor/CodeEditor.js b/src/components/CodeEditor/CodeEditor.js new file mode 100644 index 00000000..0e633593 --- /dev/null +++ b/src/components/CodeEditor/CodeEditor.js @@ -0,0 +1,282 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; +import Remarkable from 'remarkable'; +// TODO: switch back to the upstream version of react-live +// once https://github.com/FormidableLabs/react-live/issues/37 is fixed. +import {LiveProvider, LiveEditor} from '@gaearon/react-live'; +import {colors, media} from 'theme'; +import MetaTitle from 'templates/components/MetaTitle'; + +const compile = code => + Babel.transform(code, {presets: ['es2015', 'react']}).code; // eslint-disable-line no-undef + +class CodeEditor extends Component { + constructor(props, context) { + super(props, context); + + this.state = this._updateState(props.code); + } + + componentDidMount() { + // Initial render() will always be a no-op, + // Because the mountNode ref won't exist yet. + this._render(); + } + + componentDidUpdate(prevProps, prevState) { + if (prevState.compiled !== this.state.compiled) { + this._render(); + } + } + + render() { + const {children, code} = this.props; + const {error} = this.state; + + return ( + <LiveProvider code={code} mountStylesheet={false}> + <div + css={{ + [media.greaterThan('xlarge')]: { + display: 'flex', + flexDirection: 'row', + }, + + [media.lessThan('large')]: { + display: 'block', + }, + }}> + {children && ( + <div + css={{ + flex: '0 0 33%', + + [media.lessThan('xlarge')]: { + marginBottom: 20, + }, + + '& h3': { + color: colors.dark, + maxWidth: '11em', + paddingTop: 0, + }, + + '& p': { + marginTop: 15, + marginRight: 40, + lineHeight: 1.7, + + [media.greaterThan('xlarge')]: { + marginTop: 25, + }, + }, + }}> + {children} + </div> + )} + + <div + css={{ + [media.greaterThan('medium')]: { + flex: '0 0 67%', + display: 'flex', + alignItems: 'stretch', + flexDirection: 'row', + }, + + [media.lessThan('small')]: { + display: 'block', + }, + }}> + <div + css={{ + flex: '0 0 70%', + overflow: 'hidden', + borderRadius: '10px 0 0 10px', + + [media.lessThan('small')]: { + borderRadius: '10px 10px 0 0', + }, + }}> + <div + css={{ + padding: '0px 10px', + background: colors.darker, + color: colors.white, + }}> + <MetaTitle onDark={true}>Live JSX Editor</MetaTitle> + </div> + <div + css={{ + height: '100%', + width: '100%', + borderRadius: '0', + marginTop: '0 !important', + marginLeft: '0 !important', + paddingLeft: '0 !important', + marginRight: '0 !important', + paddingRight: '0 !important', + + '& pre.prism-code[contenteditable]': { + maxHeight: '280px !important', + outline: 0, + }, + }} + className="gatsby-highlight"> + <LiveEditor onChange={this._onChange} /> + </div> + </div> + {error && ( + <div + css={{ + flex: '0 0 30%', + overflow: 'hidden', + border: `1px solid ${colors.error}`, + borderRadius: '0 10px 10px 0', + fontSize: 12, + lineHeight: 1.5, + + [media.lessThan('small')]: { + borderRadius: '0 0 10px 10px', + }, + }}> + <div + css={{ + padding: '0px 10px', + background: colors.error, + color: colors.white, + }}> + <MetaTitle + cssProps={{ + color: colors.white, + }}> + Error + </MetaTitle> + </div> + <pre + css={{ + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', + color: colors.error, + padding: 10, + }}> + {error.message} + </pre> + </div> + )} + {!error && ( + <div + css={{ + flex: '0 0 30%', + overflow: 'hidden', + border: `1px solid ${colors.divider}`, + borderRadius: '0 10px 10px 0', + + [media.lessThan('small')]: { + borderRadius: '0 0 10px 10px', + }, + }}> + <div + css={{ + padding: '0 10px', + backgroundColor: colors.divider, + }}> + <MetaTitle>Result</MetaTitle> + </div> + <div + css={{ + padding: 10, + + '& input': { + width: '100%', + display: 'block', + border: '1px solid #ccc', // TODO + padding: 5, + }, + + '& button': { + marginTop: 10, + padding: '5px 10px', + }, + + '& textarea': { + width: '100%', + marginTop: 10, + height: 60, + padding: 5, + }, + }} + ref={this._setMountRef} + /> + </div> + )} + </div> + </div> + </LiveProvider> + ); + } + + _render() { + if (!this._mountNode) { + return; + } + + const {compiled} = this.state; + + try { + // Example code requires React, ReactDOM, and Remarkable to be within scope. + // It also requires a "mountNode" variable for ReactDOM.render() + // eslint-disable-next-line no-new-func + new Function('React', 'ReactDOM', 'Remarkable', 'mountNode', compiled)( + React, + ReactDOM, + Remarkable, + this._mountNode, + ); + } catch (error) { + console.error(error); + + this.setState({ + compiled: null, + error, + }); + } + } + + _setMountRef = ref => { + this._mountNode = ref; + }; + + _updateState(code) { + try { + return { + compiled: compile(code), + error: null, + }; + } catch (error) { + console.error(error); + + return { + compiled: null, + error, + }; + } + } + + _onChange = code => { + this.setState(this._updateState(code)); + }; +} + +export default CodeEditor; diff --git a/src/components/CodeEditor/index.js b/src/components/CodeEditor/index.js new file mode 100644 index 00000000..66b735cd --- /dev/null +++ b/src/components/CodeEditor/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import CodeEditor from './CodeEditor'; + +export default CodeEditor; diff --git a/src/components/Container/Container.js b/src/components/Container/Container.js new file mode 100644 index 00000000..aec40df6 --- /dev/null +++ b/src/components/Container/Container.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React from 'react'; + +import {media} from 'theme'; + +/** + * This component wraps page content sections (eg header, footer, main). + * It provides consistent margin and max width behavior. + */ +const Container = ({children}) => ( + <div + css={{ + paddingLeft: 20, + paddingRight: 20, + marginLeft: 'auto', + marginRight: 'auto', + + [media.greaterThan('medium')]: { + width: '90%', + }, + + [media.size('xxlarge')]: { + maxWidth: 1260, + }, + }}> + {children} + </div> +); + +export default Container; diff --git a/src/components/Container/index.js b/src/components/Container/index.js new file mode 100644 index 00000000..89b41c12 --- /dev/null +++ b/src/components/Container/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Container from './Container'; + +export default Container; diff --git a/src/components/ErrorDecoder/ErrorDecoder.js b/src/components/ErrorDecoder/ErrorDecoder.js new file mode 100644 index 00000000..dc227770 --- /dev/null +++ b/src/components/ErrorDecoder/ErrorDecoder.js @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; + +function replaceArgs(msg, argList) { + let argIdx = 0; + return msg.replace(/%s/g, function() { + const arg = argList[argIdx++]; + return arg === undefined ? '[missing argument]' : arg; + }); +} + +function urlify(str) { + const urlRegex = /(https:\/\/fb\.me\/[a-z\-]+)/g; + + const segments = str.split(urlRegex); + + for (let i = 0; i < segments.length; i++) { + if (i % 2 === 1) { + segments[i] = ( + <a key={i} target="_blank" rel="noopener" href={segments[i]}> + {segments[i]} + </a> + ); + } + } + + return segments; +} + +// ?invariant=123&args[]=foo&args[]=bar +function parseQueryString(location) { + const rawQueryString = location.search.substring(1); + if (!rawQueryString) { + return null; + } + + let code = ''; + let args = []; + + const queries = rawQueryString.split('&'); + for (let i = 0; i < queries.length; i++) { + const query = decodeURIComponent(queries[i]); + if (query.indexOf('invariant=') === 0) { + code = query.slice(10); + } else if (query.indexOf('args[]=') === 0) { + args.push(query.slice(7)); + } + } + + return [code, args]; +} + +function ErrorResult(props) { + const code = props.code; + const errorMsg = props.msg; + + if (!code) { + return ( + <p> + When you encounter an error, you'll receive a link to this page for that + specific error and we'll show you the full error text. + </p> + ); + } + + return ( + <div> + <p>The full text of the error you just encountered is:</p> + <code>{urlify(errorMsg)}</code> + </div> + ); +} + +class ErrorDecoder extends Component { + constructor(...args) { + super(...args); + + this.state = { + code: null, + errorMsg: '', + }; + } + + componentWillMount() { + const {errorCodesString} = this.props; + const errorCodes = JSON.parse(errorCodesString); + const parseResult = parseQueryString(this.props.location); + if (parseResult != null) { + const [code, args] = parseResult; + if (errorCodes[code]) { + this.setState({ + code: code, + errorMsg: replaceArgs(errorCodes[code], args), + }); + } + } + } + + render() { + return <ErrorResult code={this.state.code} msg={this.state.errorMsg} />; + } +} + +ErrorDecoder.propTypes = { + errorCodesString: PropTypes.string.isRequired, + location: PropTypes.object.isRequired, +}; + +export default ErrorDecoder; diff --git a/src/components/ErrorDecoder/index.js b/src/components/ErrorDecoder/index.js new file mode 100644 index 00000000..4a94850d --- /dev/null +++ b/src/components/ErrorDecoder/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import ErrorDecoder from './ErrorDecoder'; + +export default ErrorDecoder; diff --git a/src/components/Flex/Flex.js b/src/components/Flex/Flex.js new file mode 100644 index 00000000..041c4e9c --- /dev/null +++ b/src/components/Flex/Flex.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import {createElement} from 'glamor/react'; + +/** + * Convenience component for declaring a flexbox layout. + */ +const Flex = ({ + basis = 'auto', + children, + direction = 'row', + grow = 0, + halign = 'flex-start', + shrink = 1, + type = 'div', + valign = 'flex-start', + ...rest +}) => + createElement( + type, + { + css: { + display: 'flex', + flexDirection: direction, + flexGrow: grow, + flexShrink: shrink, + flexBasis: basis, + justifyContent: direction === 'row' ? halign : valign, + alignItems: direction === 'row' ? valign : halign, + }, + ...rest, + }, + children, + ); + +export default Flex; diff --git a/src/components/Flex/index.js b/src/components/Flex/index.js new file mode 100644 index 00000000..c607c7ab --- /dev/null +++ b/src/components/Flex/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Flex from './Flex'; + +export default Flex; diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js new file mode 100644 index 00000000..f8f0167f --- /dev/null +++ b/src/components/Header/Header.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React from 'react'; +import {colors, fonts} from 'theme'; + +const Header = ({children}) => ( + <h1 + css={{ + color: colors.dark, + marginRight: '5%', + ...fonts.header, + }}> + {children} + </h1> +); + +export default Header; diff --git a/src/components/Header/index.js b/src/components/Header/index.js new file mode 100644 index 00000000..f8d01569 --- /dev/null +++ b/src/components/Header/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Header from './Header'; + +export default Header; diff --git a/src/components/LayoutFooter/ExternalFooterLink.js b/src/components/LayoutFooter/ExternalFooterLink.js new file mode 100644 index 00000000..25e627e7 --- /dev/null +++ b/src/components/LayoutFooter/ExternalFooterLink.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React from 'react'; +import {colors} from 'theme'; +import ExternalLinkSvg from 'templates/components/ExternalLinkSvg'; + +const ExternalFooterLink = ({children, href, target, rel}) => ( + <a + css={{ + lineHeight: 2, + ':hover': { + color: colors.brand, + }, + }} + href={href} + target={target} + rel={rel}> + {children} + <ExternalLinkSvg + cssProps={{ + verticalAlign: -2, + display: 'inline-block', + marginLeft: 5, + color: colors.subtle, + }} + /> + </a> +); + +export default ExternalFooterLink; diff --git a/src/components/LayoutFooter/Footer.js b/src/components/LayoutFooter/Footer.js new file mode 100644 index 00000000..637352b9 --- /dev/null +++ b/src/components/LayoutFooter/Footer.js @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Container from 'components/Container'; +import ExternalFooterLink from './ExternalFooterLink'; +import FooterLink from './FooterLink'; +import FooterNav from './FooterNav'; +import MetaTitle from 'templates/components/MetaTitle'; +import React from 'react'; +import {colors, media} from 'theme'; + +import ossLogoPng from 'images/oss_logo.png'; + +const Footer = ({layoutHasSidebar = false}) => ( + <footer + css={{ + backgroundColor: colors.darker, + color: colors.white, + paddingTop: 10, + paddingBottom: 50, + + [media.size('sidebarFixed')]: { + paddingTop: 40, + }, + }}> + <Container> + <div + css={{ + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + + [media.between('small', 'medium')]: { + paddingRight: layoutHasSidebar ? 240 : null, + }, + + [media.between('large', 'largerSidebar')]: { + paddingRight: layoutHasSidebar ? 280 : null, + }, + [media.between('largerSidebar', 'sidebarFixed', true)]: { + paddingRight: layoutHasSidebar ? 380 : null, + }, + }}> + <div + css={{ + flexWrap: 'wrap', + display: 'flex', + + [media.lessThan('large')]: { + width: '100%', + }, + [media.greaterThan('xlarge')]: { + width: 'calc(100% / 3 * 2)', + paddingLeft: 40, + }, + }}> + <FooterNav layoutHasSidebar={layoutHasSidebar}> + <MetaTitle onDark={true}>Docs</MetaTitle> + <FooterLink to="/docs/hello-world.html">Quick Start</FooterLink> + <FooterLink to="/docs/thinking-in-react.html"> + Thinking in React + </FooterLink> + <FooterLink to="/tutorial/tutorial.html">Tutorial</FooterLink> + <FooterLink to="/docs/jsx-in-depth.html"> + Advanced Guides + </FooterLink> + </FooterNav> + <FooterNav layoutHasSidebar={layoutHasSidebar}> + <MetaTitle onDark={true}>Community</MetaTitle> + <ExternalFooterLink + href="http://stackoverflow.com/questions/tagged/reactjs" + target="_blank" + rel="noopener"> + Stack Overflow + </ExternalFooterLink> + <ExternalFooterLink + href="https://discuss.reactjs.org" + target="_blank" + rel="noopener"> + Discussion Forum + </ExternalFooterLink> + <ExternalFooterLink + href="https://discord.gg/0ZcbPKXt5bZjGY5n" + target="_blank" + rel="noopener"> + Reactiflux Chat + </ExternalFooterLink> + <ExternalFooterLink + href="https://www.facebook.com/react" + target="_blank" + rel="noopener"> + Facebook + </ExternalFooterLink> + <ExternalFooterLink + href="https://twitter.com/reactjs" + target="_blank" + rel="noopener"> + Twitter + </ExternalFooterLink> + </FooterNav> + <FooterNav layoutHasSidebar={layoutHasSidebar}> + <MetaTitle onDark={true}>Resources</MetaTitle> + <FooterLink to="/community/conferences.html"> + Conferences + </FooterLink> + <FooterLink to="/community/videos.html">Videos</FooterLink> + <ExternalFooterLink + href="https://github.com/facebook/react/wiki/Examples" + target="_blank" + rel="noopener"> + Examples + </ExternalFooterLink> + <ExternalFooterLink + href="https://github.com/facebook/react/wiki/Complementary-Tools" + target="_blank" + rel="noopener"> + Complementary Tools + </ExternalFooterLink> + </FooterNav> + <FooterNav layoutHasSidebar={layoutHasSidebar}> + <MetaTitle onDark={true}>More</MetaTitle> + <FooterLink to="/blog/">Blog</FooterLink> + <ExternalFooterLink + href="https://github.com/facebook/react" + target="_blank" + rel="noopener"> + GitHub + </ExternalFooterLink> + <ExternalFooterLink + href="http://facebook.github.io/react-native/" + target="_blank" + rel="noopener"> + React Native + </ExternalFooterLink> + <FooterLink to="/acknowledgements.html"> + Acknowledgements + </FooterLink> + </FooterNav> + </div> + <section + css={{ + paddingTop: 40, + display: 'block !important', // Override 'Installation' <style> specifics + + [media.greaterThan('xlarge')]: { + width: 'calc(100% / 3)', + order: -1, + }, + [media.greaterThan('large')]: { + order: -1, + width: layoutHasSidebar ? null : 'calc(100% / 3)', + }, + [media.lessThan('large')]: { + textAlign: 'center', + width: '100%', + paddingTop: 40, + }, + }}> + <a + href="https://code.facebook.com/projects/" + target="_blank" + rel="noopener"> + <img + alt="Facebook Open Source" + css={{ + maxWidth: 160, + height: 'auto', + }} + src={ossLogoPng} + /> + </a> + <p + css={{ + color: colors.subtleOnDark, + paddingTop: 15, + }}> + Copyright © 2017 Facebook Inc. + </p> + </section> + </div> + </Container> + </footer> +); + +export default Footer; diff --git a/src/components/LayoutFooter/FooterLink.js b/src/components/LayoutFooter/FooterLink.js new file mode 100644 index 00000000..c0207611 --- /dev/null +++ b/src/components/LayoutFooter/FooterLink.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Link from 'gatsby-link'; +import React from 'react'; +import {colors} from 'theme'; + +const FooterLink = ({children, target, to}) => ( + <Link + css={{ + lineHeight: 2, + ':hover': { + color: colors.brand, + }, + }} + to={to} + target={target}> + {children} + </Link> +); + +export default FooterLink; diff --git a/src/components/LayoutFooter/FooterNav.js b/src/components/LayoutFooter/FooterNav.js new file mode 100644 index 00000000..f1d0dc2b --- /dev/null +++ b/src/components/LayoutFooter/FooterNav.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React from 'react'; +import {media} from 'theme'; + +const FooterNav = ({children, title, layoutHasSidebar = false}) => ( + <div + css={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + width: '50%', + paddingTop: 40, + + [media.size('sidebarFixed')]: { + paddingTop: 0, + width: '25%', + }, + }}> + <div + css={{ + display: 'inline-flex', + flexDirection: 'column', + }}> + {children} + </div> + </div> +); + +export default FooterNav; diff --git a/src/components/LayoutFooter/index.js b/src/components/LayoutFooter/index.js new file mode 100644 index 00000000..600f8070 --- /dev/null +++ b/src/components/LayoutFooter/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Footer from './Footer'; + +export default Footer; diff --git a/src/components/LayoutHeader/Header.js b/src/components/LayoutHeader/Header.js new file mode 100644 index 00000000..37624998 --- /dev/null +++ b/src/components/LayoutHeader/Header.js @@ -0,0 +1,251 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Container from 'components/Container'; +import HeaderLink from './HeaderLink'; +import Link from 'gatsby-link'; +import React from 'react'; +import {colors, fonts, media} from 'theme'; +import {version} from 'site-constants'; +import ExternalLinkSvg from 'templates/components/ExternalLinkSvg'; + +import logoSvg from 'icons/logo.svg'; + +const Header = ({location}) => ( + <header + css={{ + backgroundColor: colors.darker, + color: colors.white, + position: 'fixed', + zIndex: 1, + width: '100%', + top: 0, + left: 0, + }}> + <Container> + <div + css={{ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + height: 60, + [media.between('small', 'large')]: { + height: 50, + }, + [media.lessThan('small')]: { + height: 40, + }, + }}> + <Link + css={{ + display: 'flex', + marginRight: 10, + height: '100%', + alignItems: 'center', + + [media.greaterThan('small')]: { + width: 'calc(100% / 6)', + }, + [media.lessThan('small')]: { + flex: '0 0 auto', + }, + }} + to="/"> + <img src={logoSvg} alt="" height="20" /> + <span + css={{ + color: colors.brand, + marginLeft: 10, + fontWeight: 700, + fontSize: 20, + lineHeight: '20px', + [media.lessThan('large')]: { + fontSize: 16, + marginTop: 1, + }, + [media.lessThan('small')]: { + // Visually hidden + position: 'absolute', + overflow: 'hidden', + clip: 'rect(0 0 0 0)', + height: 1, + width: 1, + margin: -1, + padding: 0, + border: 0, + }, + }}> + React + </span> + </Link> + + <nav + css={{ + display: 'flex', + flexDirection: 'row', + alignItems: 'stretch', + overflowX: 'auto', + overflowY: 'hidden', + WebkitOverflowScrolling: 'touch', + height: '100%', + width: '60%', + [media.size('xsmall')]: { + flexGrow: '1', + width: 'auto', + }, + [media.greaterThan('xlarge')]: { + width: null, + }, + [media.lessThan('small')]: { + maskImage: + 'linear-gradient(to right, transparent, black 20px, black 90%, transparent)', + }, + }}> + <HeaderLink + isActive={location.pathname.includes('/docs/')} + title="Docs" + to="/docs/hello-world.html" + /> + <HeaderLink + isActive={location.pathname.includes('/tutorial/')} + title="Tutorial" + to="/tutorial/tutorial.html" + /> + <HeaderLink + isActive={location.pathname.includes('/community/')} + title="Community" + to="/community/support.html" + /> + <HeaderLink + isActive={location.pathname.includes('/blog')} + title="Blog" + to="/blog/" + /> + </nav> + + <form + css={{ + display: 'flex', + flex: '0 0 auto', + flexDirection: 'row', + alignItems: 'center', + paddingLeft: '0.5rem', + paddingRight: '0.5rem', + + [media.lessThan('small')]: { + justifyContent: 'flex-end', + }, + [media.lessThan('large')]: { + marginRight: 10, + }, + [media.between('small', 'medium')]: { + width: 'calc(100% / 3)', + }, + [media.between('medium', 'xlarge')]: { + width: 'calc(100% / 6)', + }, + [media.greaterThan('small')]: { + minWidth: 120, + }, + }}> + <input + css={{ + appearance: 'none', + background: 'transparent', + border: 0, + color: colors.white, + fontSize: 18, + fontWeight: 300, + fontFamily: 'inherit', + position: 'relative', + paddingLeft: '24px', + backgroundImage: 'url(/search.svg)', + backgroundSize: '16px 16px', + backgroundRepeat: 'no-repeat', + backgroundPositionY: 'center', + backgroundPositionX: 'left', + + [media.lessThan('large')]: { + fontSize: 16, + }, + [media.greaterThan('small')]: { + width: '100%', + }, + [media.lessThan('small')]: { + width: '16px', + transition: 'width 0.2s ease, padding 0.2s ease', + paddingLeft: '16px', + + ':focus': { + paddingLeft: '24px', + width: '8rem', + outline: 'none', + }, + }, + }} + id="algolia-doc-search" + type="search" + placeholder="Search docs" + aria-label="Search docs" + /> + </form> + + <div + css={{ + [media.lessThan('medium')]: { + display: 'none', + }, + [media.greaterThan('large')]: { + width: 'calc(100% / 6)', + }, + }}> + <a + css={{ + padding: '5px 10px', + backgroundColor: colors.lighter, + borderRadius: 15, + whiteSpace: 'nowrap', + ...fonts.small, + }} + href="https://github.com/facebook/react/releases" + target="_blank" + rel="noopener"> + v{version} + </a> + <a + css={{ + padding: '5px 10px', + marginLeft: 10, + whiteSpace: 'nowrap', + ...fonts.small, + ':hover': { + color: colors.brand, + }, + }} + href="https://github.com/facebook/react/" + target="_blank" + rel="noopener"> + GitHub + <ExternalLinkSvg + cssProps={{ + marginLeft: 5, + verticalAlign: -2, + color: colors.subtle, + }} + /> + </a> + </div> + </div> + </Container> + </header> +); + +export default Header; diff --git a/src/components/LayoutHeader/HeaderLink.js b/src/components/LayoutHeader/HeaderLink.js new file mode 100644 index 00000000..917913e8 --- /dev/null +++ b/src/components/LayoutHeader/HeaderLink.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Link from 'gatsby-link'; +import React from 'react'; +import {colors, media} from 'theme'; + +const HeaderLink = ({isActive, title, to}) => ( + <Link css={[style, isActive && activeStyle]} to={to}> + {title} + {isActive && <span css={activeAfterStyle} />} + </Link> +); + +const style = { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + color: colors.white, + transition: 'color 0.2s ease-out', + paddingLeft: 15, + paddingRight: 15, + fontWeight: 300, + + [media.size('xsmall')]: { + paddingLeft: 8, + paddingRight: 8, + }, + + [media.between('small', 'medium')]: { + paddingLeft: 10, + paddingRight: 10, + }, + + [media.greaterThan('xlarge')]: { + paddingLeft: 20, + paddingRight: 20, + fontSize: 18, + + ':hover': { + color: colors.brand, + }, + }, +}; + +const activeStyle = { + color: colors.brand, + + [media.greaterThan('small')]: { + position: 'relative', + }, +}; + +const activeAfterStyle = { + [media.greaterThan('small')]: { + position: 'absolute', + bottom: -1, + height: 4, + background: colors.brand, + left: 0, + right: 0, + zIndex: 1, + }, +}; + +export default HeaderLink; diff --git a/src/components/LayoutHeader/SearchSvg.js b/src/components/LayoutHeader/SearchSvg.js new file mode 100644 index 00000000..7ccac665 --- /dev/null +++ b/src/components/LayoutHeader/SearchSvg.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React from 'react'; + +const SearchSvg = () => ( + <svg + alt="Search" + height="16" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 16 16"> + <title>Search + + +); + +export default SearchSvg; diff --git a/src/components/LayoutHeader/index.js b/src/components/LayoutHeader/index.js new file mode 100644 index 00000000..f8d01569 --- /dev/null +++ b/src/components/LayoutHeader/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Header from './Header'; + +export default Header; diff --git a/src/components/MarkdownHeader/MarkdownHeader.js b/src/components/MarkdownHeader/MarkdownHeader.js new file mode 100644 index 00000000..5b4ed3da --- /dev/null +++ b/src/components/MarkdownHeader/MarkdownHeader.js @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Flex from 'components/Flex'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {colors, fonts, media} from 'theme'; + +const MarkdownHeader = ({title}) => ( + +

+ {title} +

+
+); + +MarkdownHeader.propTypes = { + title: PropTypes.string.isRequired, +}; + +export default MarkdownHeader; diff --git a/src/components/MarkdownHeader/index.js b/src/components/MarkdownHeader/index.js new file mode 100644 index 00000000..cad7fa3c --- /dev/null +++ b/src/components/MarkdownHeader/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import MarkdownHeader from './MarkdownHeader'; + +export default MarkdownHeader; diff --git a/src/components/MarkdownPage/MarkdownPage.js b/src/components/MarkdownPage/MarkdownPage.js new file mode 100644 index 00000000..d4864461 --- /dev/null +++ b/src/components/MarkdownPage/MarkdownPage.js @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Container from 'components/Container'; +import Flex from 'components/Flex'; +import MarkdownHeader from 'components/MarkdownHeader'; +import NavigationFooter from 'templates/components/NavigationFooter'; +import PropTypes from 'prop-types'; +import React from 'react'; +import StickyResponsiveSidebar from 'components/StickyResponsiveSidebar'; +import TitleAndMetaTags from 'components/TitleAndMetaTags'; +import findSectionForPath from 'utils/findSectionForPath'; +import toCommaSeparatedList from 'utils/toCommaSeparatedList'; +import {sharedStyles} from 'theme'; +import createOgUrl from 'utils/createOgUrl'; + +const MarkdownPage = ({ + authors, + createLink, + date, + ogDescription, + location, + markdownRemark, + sectionList, + titlePostfix = '', +}) => { + const hasAuthors = authors.length > 0; + const titlePrefix = markdownRemark.frontmatter.title || ''; + + return ( + + +
+ +
+ + + + {(date || hasAuthors) && ( +
+ {date}{' '} + {hasAuthors && ( + + by{' '} + {toCommaSeparatedList(authors, author => ( + + {author.frontmatter.name} + + ))} + + )} +
+ )} + +
+
+ + {markdownRemark.fields.path && ( + + )} +
+ + +
+ +
+
+ +
+ + {/* TODO Read prev/next from index map, not this way */} + {(markdownRemark.frontmatter.next || markdownRemark.frontmatter.prev) && ( + + )} + + ); +}; + +MarkdownPage.defaultProps = { + authors: [], +}; + +// TODO Better types +MarkdownPage.propTypes = { + authors: PropTypes.array.isRequired, + createLink: PropTypes.func.isRequired, + date: PropTypes.string, + location: PropTypes.object.isRequired, + markdownRemark: PropTypes.object.isRequired, + sectionList: PropTypes.array.isRequired, +}; + +export default MarkdownPage; diff --git a/src/components/MarkdownPage/index.js b/src/components/MarkdownPage/index.js new file mode 100644 index 00000000..d3c6a193 --- /dev/null +++ b/src/components/MarkdownPage/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import MarkdownPage from './MarkdownPage'; + +export default MarkdownPage; diff --git a/src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js b/src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js new file mode 100644 index 00000000..32008cec --- /dev/null +++ b/src/components/StickyResponsiveSidebar/StickyResponsiveSidebar.js @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Container from 'components/Container'; +import {Component, React} from 'react'; +import Sidebar from 'templates/components/Sidebar'; +import {colors, media} from 'theme'; +import ChevronSvg from 'templates/components/ChevronSvg'; + +class StickyResponsiveSidebar extends Component { + constructor(props, context) { + super(props, context); + + this.state = { + open: false, + }; + this._openNavMenu = this._openNavMenu.bind(this); + this._closeNavMenu = this._closeNavMenu.bind(this); + } + + _openNavMenu() { + this.setState({open: !this.state.open}); + } + + _closeNavMenu() { + this.setState({open: false}); + } + + render() { + const {open} = this.state; + const smallScreenSidebarStyles = { + top: 0, + left: 0, + bottom: 0, + right: 0, + position: 'fixed', + backgroundColor: colors.white, + zIndex: 2, + height: '100vh', + overflowY: 'auto', + WebkitOverflowScrolling: 'touch', + pointerEvents: open ? 'auto' : 'none', + }; + + const smallScreenBottomBarStyles = { + display: 'inline-block', + }; + + const iconOffset = open ? 8 : -4; + const menuOpacity = open ? 1 : 0; + const menuOffset = open ? 0 : 40; + + // TODO: role and aria props for 'close' button? + return ( +
+
+
+ +
+
+
+ +
+
+ + +
+
+
+
+
+ ); + } +} + +export default StickyResponsiveSidebar; diff --git a/src/components/StickyResponsiveSidebar/index.js b/src/components/StickyResponsiveSidebar/index.js new file mode 100644 index 00000000..742317c0 --- /dev/null +++ b/src/components/StickyResponsiveSidebar/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import StickyResponsiveSidebar from './StickyResponsiveSidebar'; + +export default StickyResponsiveSidebar; diff --git a/src/components/TitleAndMetaTags/TitleAndMetaTags.js b/src/components/TitleAndMetaTags/TitleAndMetaTags.js new file mode 100644 index 00000000..9efae686 --- /dev/null +++ b/src/components/TitleAndMetaTags/TitleAndMetaTags.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import Helmet from 'react-helmet'; +import React from 'react'; + +const defaultDescription = 'A JavaScript library for building user interfaces'; + +const TitleAndMetaTags = ({title, ogDescription, ogUrl}) => { + return ( + + + + {ogUrl && } + + + + + ); +}; + +export default TitleAndMetaTags; diff --git a/src/components/TitleAndMetaTags/index.js b/src/components/TitleAndMetaTags/index.js new file mode 100644 index 00000000..dc26233a --- /dev/null +++ b/src/components/TitleAndMetaTags/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import TitleAndMetaTags from './TitleAndMetaTags'; + +export default TitleAndMetaTags; diff --git a/src/css/algolia.css b/src/css/algolia.css new file mode 100644 index 00000000..6dec953b --- /dev/null +++ b/src/css/algolia.css @@ -0,0 +1,453 @@ +/* Styles forked from Algolia */ + +.searchbox { + display: inline-block; + position: relative; + width: 200px; + height: 32px !important; + white-space: nowrap; + box-sizing: border-box; + visibility: visible !important; +} + +.searchbox .algolia-autocomplete { + display: block; + width: 100%; + height: 100%; +} + +.searchbox__wrapper { + width: 100%; + height: 100%; + z-index: 999; + position: relative; +} + +.searchbox__input { + display: inline-block; + box-sizing: border-box; + -webkit-transition: box-shadow .4s ease, background .4s ease; + transition: box-shadow .4s ease, background .4s ease; + border: 0; + border-radius: 16px; + box-shadow: inset 0 0 0 1px #CCCCCC; + background: #FFFFFF !important; + padding: 0; + padding-right: 26px; + padding-left: 32px; + width: 100%; + height: 100%; + vertical-align: middle; + white-space: normal; + font-size: 12px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.searchbox__input::-webkit-search-decoration, .searchbox__input::-webkit-search-cancel-button, .searchbox__input::-webkit-search-results-button, .searchbox__input::-webkit-search-results-decoration { + display: none; +} + +.searchbox__input:hover { + box-shadow: inset 0 0 0 1px #b3b3b3; +} + +.searchbox__input:focus, .searchbox__input:active { + outline: 0; + box-shadow: inset 0 0 0 1px #AAAAAA; + background: #FFFFFF; +} + +.searchbox__input::-webkit-input-placeholder { + color: #AAAAAA; +} + +.searchbox__input::-moz-placeholder { + color: #AAAAAA; +} + +.searchbox__input:-ms-input-placeholder { + color: #AAAAAA; +} + +.searchbox__input::placeholder { + color: #AAAAAA; +} + +.searchbox__submit { + position: absolute; + top: 0; + margin: 0; + border: 0; + border-radius: 16px 0 0 16px; + background-color: rgba(69, 142, 225, 0); + padding: 0; + width: 32px; + height: 100%; + vertical-align: middle; + text-align: center; + font-size: inherit; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + right: inherit; + left: 0; +} + +.searchbox__submit::before { + display: inline-block; + margin-right: -4px; + height: 100%; + vertical-align: middle; + content: ''; +} + +.searchbox__submit:hover, .searchbox__submit:active { + cursor: pointer; +} + +.searchbox__submit:focus { + outline: 0; +} + +.searchbox__submit svg { + width: 14px; + height: 14px; + vertical-align: middle; + fill: #6D7E96; +} + +.searchbox__reset { + display: block; + position: absolute; + top: 8px; + right: 8px; + margin: 0; + border: 0; + background: none; + cursor: pointer; + padding: 0; + font-size: inherit; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + fill: rgba(0, 0, 0, 0.5); +} + +.searchbox__reset.hide { + display: none; +} + +.searchbox__reset:focus { + outline: 0; +} + +.searchbox__reset svg { + display: block; + margin: 4px; + width: 8px; + height: 8px; +} + +.searchbox__input:valid ~ .searchbox__reset { + display: block; + -webkit-animation-name: sbx-reset-in; + animation-name: sbx-reset-in; + -webkit-animation-duration: .15s; + animation-duration: .15s; +} + +@-webkit-keyframes sbx-reset-in { + 0% { + -webkit-transform: translate3d(-20%, 0, 0); + transform: translate3d(-20%, 0, 0); + opacity: 0; + } + 100% { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes sbx-reset-in { + 0% { + -webkit-transform: translate3d(-20%, 0, 0); + transform: translate3d(-20%, 0, 0); + opacity: 0; + } + 100% { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + + +.algolia-autocomplete .ds-dropdown-menu:before { + display: block; + position: absolute; + content: ''; + width: 14px; + height: 14px; + background: #373940; + z-index: 1000; + top: -7px; + border-top: 1px solid #373940; + border-right: 1px solid #373940; + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + border-radius: 2px; +} + +.algolia-autocomplete .ds-dropdown-menu { + box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2), 0 2px 3px 0 rgba(0, 0, 0, 0.1); +} + +@media (min-width: 601px) { + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu { + right: 0 !important; + left: inherit !important; + } + + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu:before { + right: 48px; + } + + .algolia-autocomplete .ds-dropdown-menu { + position: relative; + top: -6px; + border-radius: 4px; + margin: 6px 0 0; + padding: 0; + text-align: left; + height: auto; + position: relative; + background: transparent; + border: none; + z-index: 999; + max-width: 600px; + min-width: 500px; + } +} + +@media (max-width: 600px) { + .algolia-autocomplete .ds-dropdown-menu { + z-index: 100; + position: fixed !important; + top: 40px !important; + left: auto !important; + right: 1rem !important; + width: 600px; + max-width: calc(100% - 2rem); + max-height: calc(100% - 5rem); + display: block; + } + + .algolia-autocomplete .ds-dropdown-menu:before { + right: 6rem; + } +} + +.algolia-autocomplete .ds-dropdown-menu .ds-suggestions { + position: relative; + z-index: 1000; +} + +.algolia-autocomplete .ds-dropdown-menu .ds-suggestion { + cursor: pointer; +} + +.algolia-autocomplete .ds-dropdown-menu [class^="ds-dataset-"] { + position: relative; + background: #fff; + border-radius: 4px; + overflow: auto; + padding: 0; +} + +.algolia-autocomplete .ds-dropdown-menu * { + box-sizing: border-box; +} + +.algolia-autocomplete .algolia-docsearch-suggestion { + position: relative; + padding: 0; + overflow: hidden; +} + +.algolia-autocomplete .ds-cursor .algolia-docsearch-suggestion--wrapper { + background: #f1f1f1; + box-shadow: inset -2px 0 0 #61dafb; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--highlight { + background: #ffe564; + padding: 0.1em 0.05em; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--category-header-lvl0 .algolia-docsearch-suggestion--highlight, +.algolia-autocomplete .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--category-header-lvl1 .algolia-docsearch-suggestion--highlight { + color: inherit; + background: inherit; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { + padding: 0 0 1px; + background: inherit; + box-shadow: inset 0 -2px 0 0 rgba(69, 142, 225, 0.8); + color: inherit; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--content { + display: block; + float: right; + width: 70%; + position: relative; + padding: 5.33333px 0 5.33333px 10.66667px; + cursor: pointer; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--content:before { + content: ''; + position: absolute; + display: block; + top: 0; + height: 100%; + width: 1px; + background: #ececec; + left: -1px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--category-header { + position: relative; + display: none; + font-size: 14px; + letter-spacing: 0.08em; + font-weight: 700; + background-color: #373940; + text-transform: uppercase; + color: #fff; + margin: 0; + padding: 5px 8px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--wrapper { + width: 100%; + float: left; + padding: 8px 0 0 0; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { + float: left; + width: 30%; + display: none; + padding-left: 0; + text-align: right; + position: relative; + padding: 5.33333px 10.66667px; + color: #777; + font-size: 0.9em; + word-wrap: break-word; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column:before { + content: ''; + position: absolute; + display: block; + top: 0; + height: 100%; + width: 1px; + background: #ececec; + right: 0; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column .algolia-docsearch-suggestion--highlight { + background-color: inherit; + color: inherit; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-inline { + display: none; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--title { + margin-bottom: 4px; + color: #02060C; + font-size: 0.9em; + font-weight: bold; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--text { + display: block; + line-height: 1.2em; + font-size: 0.85em; + color: #63676D; + padding-right: 2px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--no-results { + width: 100%; + padding: 8px 0; + text-align: center; + font-size: 1.2em; + background-color: #373940; + margin-top: -8px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--no-results .algolia-docsearch-suggestion--text { + color: #ffffff; + margin-top: 4px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--no-results::before { + display: none; +} + +.algolia-autocomplete .algolia-docsearch-suggestion code { + padding: 1px 5px; + font-size: 90%; + border: none; + color: #222222; + background-color: #EBEBEB; + border-radius: 3px; + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} + +.algolia-autocomplete .algolia-docsearch-suggestion code .algolia-docsearch-suggestion--highlight { + background: none; +} + +.algolia-autocomplete .algolia-docsearch-suggestion.algolia-docsearch-suggestion__main .algolia-docsearch-suggestion--category-header { + display: block; +} + +.algolia-autocomplete .algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary .algolia-docsearch-suggestion--subcategory-column { + display: block; +} + + +.algolia-autocomplete .algolia-docsearch-footer { + width: 110px; + height: 30px; + z-index: 2000; + float: right; + font-size: 0; + line-height: 0; +} + +.algolia-autocomplete .algolia-docsearch-footer--logo { + background-image: url('data:image/svg+xml;utf8,'); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; + overflow: hidden; + text-indent: -9000px; + width: 100%; + height: 100%; + display: block; + margin-left: -5px; +} diff --git a/src/css/reset.css b/src/css/reset.css new file mode 100644 index 00000000..9a89c69d --- /dev/null +++ b/src/css/reset.css @@ -0,0 +1,42 @@ +html { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, + "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", + "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; + font-weight: 400; + font-style: normal; + -webkit-font-smoothing: antialiased; +} + +body { + overflow-x: hidden; + position: relative; +} + +* { + margin: 0; + padding: 0; +} + +*, *::before, *::after { + box-sizing: inherit; +} + +a { + color: inherit; + text-decoration: none; +} + +ul, ol { + list-style: none; +} + +img { + display: inline-block; + vertical-align: top; +} + +pre, code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} diff --git a/src/html.js b/src/html.js new file mode 100644 index 00000000..38323f83 --- /dev/null +++ b/src/html.js @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core +*/ + +'use strict'; + +import React, {Component} from 'react'; + +let stylesStr; +if (process.env.NODE_ENV === `production`) { + try { + stylesStr = require(`!raw-loader!../public/styles.css`); + } catch (e) { + console.log(e); + } +} + +const JS_NPM_URLS = [ + '//unpkg.com/docsearch.js@2.4.1/dist/cdn/docsearch.min.js', + '//unpkg.com/babel-standalone@6.26.0/babel.min.js', +]; + +export default class HTML extends Component { + render() { + let css; + if (process.env.NODE_ENV === 'production') { + css = ( +