diff --git a/.circleci/config.yml b/.circleci/config.yml index 3c67a6ed490bce..d9da5f40aba9ed 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -182,14 +182,20 @@ jobs: - *restore_yarn_cache - *install_js - run: - name: Can we generate the material-ui build? + name: Can we generate the @material-ui/core build? command: cd packages/material-ui && yarn build + - run: + name: Can we generate the @material-ui/styles build? + command: cd packages/material-ui-styles && yarn build + - run: + name: Can we generate the @material-ui/utils build? + command: cd packages/material-ui-utils && yarn build - run: name: Can we build the docs? command: yarn docs:build - run: name: Is the size acceptable? - command: yarn size + command: yarn lerna bootstrap && yarn size test_browser: <<: *defaults steps: diff --git a/.eslintignore b/.eslintignore index 0777d22a230b81..68cab7b27ba9b9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,8 +6,6 @@ /examples/create-react-app-with-flow/flow /examples/create-react-app-with-flow/flow-typed /examples/gatsby/public -/flow -/flow-typed /packages/material-ui-codemod/lib /packages/material-ui-codemod/src/*/*.test /packages/material-ui-codemod/src/*/*.test.js diff --git a/.size-limit.js b/.size-limit.js index 3d999b239eb9b1..feffa88a5c1e0f 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -19,16 +19,22 @@ module.exports = [ limit: '18.5 KB', }, { - name: 'The size of all the material-ui modules.', + name: 'The size of the @material-ui/core modules', webpack: true, path: 'packages/material-ui/build/index.js', limit: '94.1 KB', }, + { + name: 'The size of the @material-ui/styles modules', + webpack: true, + path: 'packages/material-ui-styles/build/index.js', + limit: '14.9 KB', + }, { name: 'The main docs bundle', webpack: false, path: main.path, - limit: '185 KB', + limit: '190 KB', }, { name: 'The docs home page', diff --git a/BACKERS.md b/BACKERS.md index 5a0373edd5e054..2bd7760ce3085b 100644 --- a/BACKERS.md +++ b/BACKERS.md @@ -68,7 +68,7 @@ via [Patreon](https://www.patreon.com/oliviertassinari) | Avétis KAZARIAN | Withinpixels | SIM KIM SIA | Renaud Bompuis | Yaron Malin | | Arvanitis Panagiotis | Jesse Weigel | Bogdan Mihai Nicolae | Dung Tran | Kyle Pennell | | Kai Mit Pansen | Eric Nagy | Karens Grigorjancs | Mohamed Turco | Haroun Serang | -| Antonio Seveso | Ali Akhavan | Bruno Winck | Alessandro Annini | +| Antonio Seveso | Ali Akhavan | Bruno Winck | Alessandro Annini | Victor Irzak | via [OpenCollective](https://opencollective.com/material-ui) diff --git a/babel.config.js b/babel.config.js index bdc42ca75b9c14..f727e37ec2bd1d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -27,6 +27,14 @@ if (process.env.BABEL_ENV === 'es') { ]; } +const defaultAlias = { + '@material-ui/core': './packages/material-ui/src', + '@material-ui/icons': './packages/material-ui-icons/src', + '@material-ui/lab': './packages/material-ui-lab/src', + '@material-ui/styles': './packages/material-ui-styles/src', + '@material-ui/utils': './packages/material-ui-utils/src', +}; + module.exports = { presets: defaultPresets.concat(['@babel/preset-react']), plugins: [ @@ -42,6 +50,18 @@ module.exports = { '@babel/plugin-transform-runtime', ], env: { + test: { + sourceMaps: 'both', + plugins: [ + [ + 'babel-plugin-module-resolver', + { + root: ['./'], + alias: defaultAlias, + }, + ], + ], + }, coverage: { plugins: [ 'babel-plugin-istanbul', @@ -49,10 +69,7 @@ module.exports = { 'babel-plugin-module-resolver', { root: ['./'], - alias: { - '@material-ui/core': './packages/material-ui/src', - '@material-ui/icons': './packages/material-ui-icons/src', - }, + alias: defaultAlias, }, ], ], @@ -76,10 +93,8 @@ module.exports = { 'babel-plugin-module-resolver', { alias: { - '@material-ui/core': './packages/material-ui/src', + ...defaultAlias, '@material-ui/docs': './packages/material-ui-docs/src', - '@material-ui/icons': './packages/material-ui-icons/src', - '@material-ui/lab': './packages/material-ui-lab/src', docs: './docs', modules: './modules', pages: './pages', @@ -97,10 +112,8 @@ module.exports = { 'babel-plugin-module-resolver', { alias: { - '@material-ui/core': './packages/material-ui/src', + ...defaultAlias, '@material-ui/docs': './packages/material-ui-docs/src', - '@material-ui/icons': './packages/material-ui-icons/src', - '@material-ui/lab': './packages/material-ui-lab/src', docs: './docs', modules: './modules', pages: './pages', @@ -160,20 +173,5 @@ module.exports = { ], ], }, - test: { - sourceMaps: 'both', - plugins: [ - [ - 'babel-plugin-module-resolver', - { - root: ['./'], - alias: { - '@material-ui/core': './packages/material-ui/src', - '@material-ui/icons': './packages/material-ui-icons/src', - }, - }, - ], - ], - }, }, }; diff --git a/crowdin.yml b/crowdin.yml index 272ffd1325a29e..2d17c709d3f54b 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -2,5 +2,5 @@ files: - source: /docs/src/pages/**/*.md ignore: - /**/%file_name%-%two_letters_code%.md - - /docs/src/pages/page-layout-examples/**/* + - /docs/src/pages/getting-started/page-layout-examples/**/* translation: /%original_path%/%file_name%-%two_letters_code%.%file_extension% diff --git a/docs/src/modules/components/AppTableOfContents.js b/docs/src/modules/components/AppTableOfContents.js index caae85c6ebc151..ec6aa732a0873f 100644 --- a/docs/src/modules/components/AppTableOfContents.js +++ b/docs/src/modules/components/AppTableOfContents.js @@ -4,6 +4,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Link from 'docs/src/modules/components/Link'; import marked from 'marked'; +import warning from 'warning'; import throttle from 'lodash/throttle'; import EventListener from 'react-event-listener'; import { withStyles } from '@material-ui/core/styles'; @@ -22,6 +23,10 @@ renderer.heading = (text, level) => { children: [], }); } else if (level === 3) { + if (!itemsServer[itemsServer.length - 1]) { + throw new Error(`Missing parent level for: ${text}`); + } + itemsServer[itemsServer.length - 1].children.push({ text, level, @@ -61,6 +66,14 @@ const styles = theme => ({ }, }); +function checkDuplication(uniq, item) { + warning(!uniq[item.hash], `Duplicated table of content ${item.hash}`); + + if (!uniq[item.hash]) { + uniq[item.hash] = true; + } +} + class AppTableOfContents extends React.Component { handleScroll = throttle(() => { this.findActiveIndex(); @@ -80,8 +93,10 @@ class AppTableOfContents extends React.Component { componentDidMount() { this.itemsClient = []; + const uniq = {}; itemsServer.forEach(item2 => { + checkDuplication(uniq, item2); this.itemsClient.push({ ...item2, node: document.getElementById(item2.hash), @@ -89,6 +104,7 @@ class AppTableOfContents extends React.Component { if (item2.children.length > 0) { item2.children.forEach(item3 => { + checkDuplication(uniq, item3); this.itemsClient.push({ ...item3, node: document.getElementById(item3.hash), diff --git a/docs/src/modules/components/AppWrapper.js b/docs/src/modules/components/AppWrapper.js index cd3fd1abe12440..df77ca9bd6728a 100644 --- a/docs/src/modules/components/AppWrapper.js +++ b/docs/src/modules/components/AppWrapper.js @@ -3,8 +3,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { MuiThemeProvider } from '@material-ui/core/styles'; -import JssProvider from 'react-jss/lib/JssProvider'; +import { ThemeProvider, StylesProvider } from '@material-ui/styles'; import { lightTheme, darkTheme, setPrismTheme } from '@material-ui/docs/MarkdownElement/prism'; import getPageContext, { updatePageContext } from 'docs/src/modules/styles/getPageContext'; import GoogleAnalytics from 'docs/src/modules/components/GoogleAnalytics'; @@ -79,16 +78,17 @@ class AppWrapper extends React.Component { const { pageContext } = this.state; return ( - - + {children} - - + + ); } } diff --git a/docs/src/modules/components/Demo.js b/docs/src/modules/components/Demo.js index 1348a91abf73f7..fa03648916b445 100644 --- a/docs/src/modules/components/Demo.js +++ b/docs/src/modules/components/Demo.js @@ -312,7 +312,7 @@ Demo.propTypes = { demoOptions: PropTypes.object.isRequired, githubLocation: PropTypes.string.isRequired, index: PropTypes.number.isRequired, - js: PropTypes.func.isRequired, + js: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), raw: PropTypes.string.isRequired, }; diff --git a/docs/src/modules/components/DemoFrame.js b/docs/src/modules/components/DemoFrame.js index 8d8af6b4509bc7..79e638cb28899b 100644 --- a/docs/src/modules/components/DemoFrame.js +++ b/docs/src/modules/components/DemoFrame.js @@ -7,9 +7,9 @@ import { jssPreset, MuiThemeProvider, } from '@material-ui/core/styles'; +import { StylesProvider } from '@material-ui/styles'; import rtl from 'jss-rtl'; import Frame from 'react-frame-component'; -import JssProvider from 'react-jss/lib/JssProvider'; const styles = theme => ({ root: { @@ -52,13 +52,13 @@ class DemoFrame extends React.Component { const { children, classes, theme } = this.props; const inIframe = this.state.ready ? ( - + {React.cloneElement(children, { container: this.state.container, })} - + ) : null; return ( diff --git a/docs/src/modules/components/bootstrap.js b/docs/src/modules/components/bootstrap.js new file mode 100644 index 00000000000000..adbc5803a69392 --- /dev/null +++ b/docs/src/modules/components/bootstrap.js @@ -0,0 +1,3 @@ +import { install } from '@material-ui/styles'; + +install(); diff --git a/docs/src/modules/components/withRoot.js b/docs/src/modules/components/withRoot.js index 72462554473830..b89b1c360d2367 100644 --- a/docs/src/modules/components/withRoot.js +++ b/docs/src/modules/components/withRoot.js @@ -2,6 +2,7 @@ import 'core-js/modules/es6.array.find-index'; import 'core-js/modules/es6.set'; +import './bootstrap'; import React from 'react'; import PropTypes from 'prop-types'; import find from 'lodash/find'; @@ -40,18 +41,21 @@ const pages = [ pathname: '/getting-started/usage', }, { - pathname: '/getting-started/supported-components', - }, - { - pathname: '/getting-started/supported-platforms', + pathname: '/getting-started/example-projects', }, { - pathname: '/getting-started/example-projects', + pathname: '/getting-started/page-layout-examples', }, { pathname: '/getting-started/faq', title: 'Frequently Asked Questions', }, + { + pathname: '/getting-started/supported-components', + }, + { + pathname: '/getting-started/supported-platforms', + }, { pathname: '/getting-started/comparison', title: 'Comparison With Other Libraries', @@ -128,6 +132,22 @@ const pages = [ ...findPages[0], title: 'Component API', }, + { + pathname: '/css-in-js', + title: 'CSS in JS (experimental)', + children: [ + { + pathname: '/css-in-js/basics', + }, + { + pathname: '/css-in-js/advanced', + }, + { + pathname: '/css-in-js/api', + title: 'API', + }, + ], + }, { pathname: '/customization', children: [ @@ -185,18 +205,11 @@ const pages = [ pathname: '/guides/right-to-left', title: 'Right-to-left', }, - { - pathname: '/guides/csp', - title: 'Content Security Policy', - }, ], }, { pathname: '/premium-themes', }, - { - pathname: '/page-layout-examples', - }, { pathname: '/lab', children: [ @@ -213,7 +226,10 @@ const pages = [ { pathname: '/lab/toggle-button', }, - findPages[2].children[1], + { + ...findPages[2].children[1], + title: 'API', + }, ], }, { @@ -319,19 +335,22 @@ function withRoot(Component) { } const activePage = findActivePage(pages, { ...router, pathname }); + // Add the strict mode back once the number of warnings is manageable. + // We might miss important warnings by keeping the strict mode 🌊🌊🌊. + // + // + return ( - - - - - - - - - + + + + + + + ); } } diff --git a/docs/src/modules/styles/getPageContext.js b/docs/src/modules/styles/getPageContext.js index 0f50f4c2623da9..91d15579cffae6 100644 --- a/docs/src/modules/styles/getPageContext.js +++ b/docs/src/modules/styles/getPageContext.js @@ -41,8 +41,8 @@ const theme = getTheme({ // Configure JSS const jss = create({ - insertionPoint: 'insertion-point-jss', plugins: [...jssPreset().plugins, rtl()], + insertionPoint: 'insertion-point-jss', }); function createPageContext() { diff --git a/docs/src/pages/css-in-js/advanced/StringTemplates.js b/docs/src/pages/css-in-js/advanced/StringTemplates.js new file mode 100644 index 00000000000000..04e5095f80e1ae --- /dev/null +++ b/docs/src/pages/css-in-js/advanced/StringTemplates.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { jssPreset, StylesProvider, makeStyles } from '@material-ui/styles'; +import { create } from 'jss'; +import jssTemplate from 'jss-template'; +import Button from '@material-ui/core/Button'; + +const jss = create({ + plugins: [jssTemplate(), ...jssPreset().plugins], +}); + +const useStyles = makeStyles({ + root: ` + background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%); + border-radius: 3; + border: 0; + color: white; + height: 48px; + padding: 0 30px; + box-shadow: 0 3px 5px 2px rgba(255, 105, 135, 0.3); + `, +}); + +function Child() { + const classes = useStyles(); + return ; +} + +function StringTemplates() { + return ( + + + + ); +} + +export default StringTemplates; diff --git a/docs/src/pages/css-in-js/advanced/ThemeNesting.js b/docs/src/pages/css-in-js/advanced/ThemeNesting.js new file mode 100644 index 00000000000000..90c203f6e8dc2d --- /dev/null +++ b/docs/src/pages/css-in-js/advanced/ThemeNesting.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { ThemeProvider, makeStyles } from '@material-ui/styles'; +import Button from '@material-ui/core/Button'; + +const useStyles = makeStyles(theme => ({ + root: { + background: theme.background, + border: 0, + borderRadius: 3, + boxShadow: theme.boxShadow, + color: 'white', + height: 48, + padding: '0 30px', + }, +})); + +function DeepChild() { + const classes = useStyles(); + + return ; +} + +function Theme() { + return ( +
+ + +
+
+ ({ + ...outerTheme, + background: 'linear-gradient(45deg, #2196F3 30%, #21CBF3 90%)', + boxShadow: '0 3px 5px 2px rgba(33, 203, 243, .3)', + })} + > + + +
+
+ ); +} + +export default Theme; diff --git a/docs/src/pages/css-in-js/advanced/Theming.js b/docs/src/pages/css-in-js/advanced/Theming.js new file mode 100644 index 00000000000000..b05fcc47781f7e --- /dev/null +++ b/docs/src/pages/css-in-js/advanced/Theming.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { ThemeProvider, makeStyles } from '@material-ui/styles'; +import Button from '@material-ui/core/Button'; + +const useStyles = makeStyles(theme => ({ + root: { + background: theme.background, + border: 0, + borderRadius: 3, + boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)', + color: 'white', + height: 48, + padding: '0 30px', + }, +})); + +function DeepChild() { + const classes = useStyles(); + + return ; +} + +function Theming() { + return ( + + + + ); +} + +export default Theming; diff --git a/docs/src/pages/css-in-js/advanced/UseTheme.js b/docs/src/pages/css-in-js/advanced/UseTheme.js new file mode 100644 index 00000000000000..80b11264184ca5 --- /dev/null +++ b/docs/src/pages/css-in-js/advanced/UseTheme.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { ThemeProvider, useTheme } from '@material-ui/styles'; +import Button from '@material-ui/core/Button'; + +function DeepChild() { + const theme = useTheme(); + + return ; +} + +function UseTheme() { + return ( + + + + ); +} + +export default UseTheme; diff --git a/docs/src/pages/css-in-js/advanced/WithTheme.js b/docs/src/pages/css-in-js/advanced/WithTheme.js new file mode 100644 index 00000000000000..fa90a27fa561e3 --- /dev/null +++ b/docs/src/pages/css-in-js/advanced/WithTheme.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ThemeProvider, withTheme } from '@material-ui/styles'; +import Button from '@material-ui/core/Button'; + +function DeepChildRaw(props) { + return ; +} + +DeepChildRaw.propTypes = { + theme: PropTypes.object.isRequired, +}; + +const DeepChild = withTheme()(DeepChildRaw); + +function WithTheme() { + return ( + + + + ); +} + +export default WithTheme; diff --git a/docs/src/pages/css-in-js/advanced/advanced.md b/docs/src/pages/css-in-js/advanced/advanced.md new file mode 100644 index 00000000000000..92ba74e65a422b --- /dev/null +++ b/docs/src/pages/css-in-js/advanced/advanced.md @@ -0,0 +1,313 @@ +# Advanced + +

Advanced Usage.

+ +## Theming + +Add a `ThemeProvider` to the top level of your app to access the theme down the React's component tree. Then, you can access the theme object in the style functions. + +{{"demo": "pages/css-in-js/advanced/Theming.js"}} + +## Accessing the theme in a component + +You might need to access the theme variables inside your React components. + +### `useTheme` hook + +{{"demo": "pages/css-in-js/advanced/UseTheme.js"}} + +### `withTheme` HOC + +{{"demo": "pages/css-in-js/advanced/WithTheme.js"}} + +## Theme nesting + +You can nest multiple theme providers. +This can be really useful when dealing with different area of your application that have distinct appearance from each other. + +{{"demo": "pages/css-in-js/advanced/ThemeNesting.js"}} + +## JSS plugins + +JSS uses the concept of plugins to extend its core, allowing people to cherry-pick the features they need. +You pay the performance overhead for only what's you are using. +All the plugins aren't available by default. We have added the following list: + +- [jss-global](http://cssinjs.org/jss-global/) +- [jss-nested](http://cssinjs.org/jss-nested/) +- [jss-camel-case](http://cssinjs.org/jss-camel-case/) +- [jss-default-unit](http://cssinjs.org/jss-default-unit/) +- [jss-vendor-prefixer](http://cssinjs.org/jss-vendor-prefixer/) +- [jss-props-sort](http://cssinjs.org/jss-props-sort/) + +It's a subset of [jss-preset-default](http://cssinjs.org/jss-preset-default/). +Of course, you are free to add a new plugin. Here is an example with the [jss-rtl](https://github.com/alitaheri/jss-rtl) plugin. + +```jsx +import { create } from 'jss'; +import { StylesProvider, jssPreset } from '@material-ui/styles'; +import rtl from 'jss-rtl' + +const jss = create({ + plugins: [...jssPreset().plugins, rtl()], +}); + +function App() { + return ( + + ... + + ); +} + +export default App; +``` + +## String templates + +If you prefer using the CSS syntax, you can use the [jss-template](https://cssinjs.org/jss-template) plugin. + +```jsx +const useStyles = makeStyles({ + root: ` + background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%); + border-radius: 3; + border: 0; + color: white; + height: 48px; + padding: 0 30px; + box-shadow: 0 3px 5px 2px rgba(255, 105, 135, 0.3); + `, +}); +``` + +{{"demo": "pages/css-in-js/advanced/StringTemplates.js"}} + +## CSS injection order + +The CSS injected by Material-UI to style a component has the highest specificity possible as the `` is injected at the bottom of the `` to ensure the components always render correctly. + +You might, however, also want to override these styles, for example with styled-components. +If you are experiencing a CSS injection order issue, JSS [provides a mechanism](https://github.com/cssinjs/jss/blob/master/docs/setup.md#specify-dom-insertion-point) to handle this situation. +By adjusting the placement of the `insertionPoint` within your HTML head you can [control the order](http://cssinjs.org/js-api/#attach-style-sheets-in-a-specific-order) that the CSS rules are applied to your components. + +### HTML comment + +The simplest approach is to add an HTML comment that determines where JSS will inject the styles: + +```jsx + + + + +``` + +```jsx +import { create } from 'jss'; +import { StylesProvider, jssPreset } from '@material-ui/styles'; + +const jss = create({ + ...jssPreset(), + // We define a custom insertion point that JSS will look for injecting the styles in the DOM. + insertionPoint: 'jss-insertion-point', +}); + +function App() { + return ...; +} + +export default App; +``` + +### Other HTML element + +[Create React App](https://github.com/facebook/create-react-app) strips HTML comments when creating the production build. +To get around the issue, you can provide a DOM element (other than a comment) as the JSS insertion point. + +For example, a `