diff --git a/.circleci/config.yml b/.circleci/config.yml
index e8fd265bc9edab..ffebc6f31a5cc6 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -161,6 +161,12 @@ jobs:
- *restore_yarn_offline_mirror
- *restore_yarn_cache
- *install_js
+ - run:
+ name: Transpile TypeScript demos
+ command: yarn docs:typescript:formatted
+ - run:
+ name: Are the compiled TypeScript demos equivalent to the JavaScript demos?
+ command: git diff --exit-code
- run:
name: Can we generate the @material-ui/core build?
command: cd packages/material-ui && yarn build
diff --git a/.eslintignore b/.eslintignore
index 68cab7b27ba9b9..e116eed0edf14f 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -6,6 +6,7 @@
/examples/create-react-app-with-flow/flow
/examples/create-react-app-with-flow/flow-typed
/examples/gatsby/public
+/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/
/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 d09b99535e6bca..f2ceb92d274eb0 100644
--- a/.size-limit.js
+++ b/.size-limit.js
@@ -74,7 +74,7 @@ module.exports = [
name: 'The main docs bundle',
webpack: false,
path: main.path,
- limit: '191 KB',
+ limit: '193 KB',
},
{
name: 'The docs home page',
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 24cfa4f424988d..4b80468eb3f951 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,6 +50,8 @@ We will only accept a pull request for which all tests pass. Make sure the follo
- If API documentation is being changed in the source, `yarn docs:api` was run.
- If prop types were changed, the TypeScript declarations were updated.
- If TypeScript declarations were changed, `yarn typescript` passed.
+- If demos were changed, make sure `yarn docs:typescript:formatted` does not introduce changes.
+ See [About TypeScript demos](#about-typescript-demos).
- The PR title follows the pattern `[Component] Imperative commit message`. (See: [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/#imperative) for a great explanation)
## Getting started
@@ -80,6 +82,8 @@ yarn
yarn docs:dev
```
You can now access the documentation site [locally](http://localhost:3000).
+Changes to the docs will hot reload the site. If you make changes to TypeScript files
+in the docs run `yarn docs:typescript --watch` in a separate terminal.
Test coverage is limited at present, but where possible, please add tests for any changes you make. Tests can be run with `yarn test`.
@@ -118,6 +122,14 @@ docs/src/pages/demos/buttons/
```
And let's give it a name: `SuperButtons.js`.
+We try to also document how to use this library with TypeScript. If you are familiar with
+that language try writing that demo in TypeScript in a *.tsx file. When you're done
+run `yarn docs:typescript:formatted` to automatically add a JavaScript version.
+
+Apart from the inherent pros and cons of TypeScript the demos are also used to test our
+type declarations. This helps a lot in catching regressions when updating our type
+declarations.
+
#### 2. Edit the page Markdown file.
The Markdown file is the source for the website documentation. So, whatever you wrote there will be reflected on the website.
@@ -155,6 +167,20 @@ Then, you will need to add the following code:
In case you missed something, [we have a real example that can be used as a summary report]((https://github.com/mui-org/material-ui/pull/8922/files)).
+### About TypeScript demos
+
+To help people use this library with TypeScript we try to provide equivalent demos
+in TypeScript.
+
+Changing demos in JavaScript requires a manual update of the TypeScript
+version. If you are not familiar with this language you can add the filepath
+of the TS demo to `docs/ts-demo-ignore.json`. See `docs/babel.config.ts.js` for more
+information. Otherwise our CI will fail the `test_build` job.
+A contributor can later update the TypeScript version of that demo.
+
+If you are already familiar with TypeScript you can simply write the demo in TypeScript.
+`yarn docs:typescript:formatted` will transpile it down to JavaScript.
+
## How do I use my local distribution of material-ui in any project?
Sometimes it is good to test your changes in a real world scenario, in order to do that you can install your local distribution of Material-UI in any project with [yarn link](https://yarnpkg.com/lang/en/docs/cli/link/).
diff --git a/docs/babel.config.ts.js b/docs/babel.config.ts.js
new file mode 100644
index 00000000000000..39c0751e420c9a
--- /dev/null
+++ b/docs/babel.config.ts.js
@@ -0,0 +1,24 @@
+const path = require('path');
+const ignoredDemos = require('./ts-demo-ignore.json');
+
+/**
+ * babel config to transpile tsx demos to js
+ *
+ * Can be used to spot differences between ts and js demos which might indicate that they
+ * do different things at runtime.
+ *
+ * Demos listed in ts-demo-ignore are not transpiled. Their path should be relative
+ * to `${workspaceRoot}/docs/src/pages/demos`.
+ */
+
+const workspaceRoot = path.join(__dirname, '../');
+const ignore = ignoredDemos.map(demoPath =>
+ path.join(workspaceRoot, 'docs/src/pages/demos', `${demoPath}.tsx`),
+);
+
+module.exports = {
+ presets: ['@babel/preset-typescript'],
+ plugins: ['unwrap-createStyles'],
+ ignore,
+ generatorOpts: { retainLines: true },
+};
diff --git a/docs/scripts/formattedTSDemos.js b/docs/scripts/formattedTSDemos.js
new file mode 100644
index 00000000000000..2a5998ce2ffba6
--- /dev/null
+++ b/docs/scripts/formattedTSDemos.js
@@ -0,0 +1,55 @@
+/**
+ * Transpiles and formats TS demos.
+ * Can be used to verify that JS and TS demos are equivalent. No introduced change
+ * would indicate equivalence.
+ */
+const childProcess = require('child_process');
+const fse = require('fs-extra');
+const path = require('path');
+const prettier = require('prettier');
+const util = require('util');
+
+const exec = util.promisify(childProcess.exec);
+
+async function getUnstagedGitFiles() {
+ const { stdout } = await exec('git diff --name-only');
+ const list = stdout.trim();
+
+ if (list === '') {
+ // "".split(" ") => [""]
+ return [];
+ }
+
+ return list.split('\n');
+}
+
+function fixBabelGeneratorIssues(source) {
+ return source.replace(/,\n\n/g, ',\n');
+}
+
+exec('yarn docs:typescript')
+ .then(() => {
+ const prettierConfigPath = path.join(__dirname, '../../prettier.config.js');
+ const prettierConfig = prettier.resolveConfig(process.cwd(), { config: prettierConfigPath });
+
+ return Promise.all([getUnstagedGitFiles(), prettierConfig]);
+ })
+ .then(([changedDemos, prettierConfig]) =>
+ Promise.all(
+ changedDemos.map(filename => {
+ const filepath = path.join(process.cwd(), filename);
+
+ return fse.readFile(filepath).then(source => {
+ const prettified = prettier.format(source.toString(), { ...prettierConfig, filepath });
+ const formatted = fixBabelGeneratorIssues(prettified);
+
+ return fse.writeFile(filepath, formatted);
+ });
+ }),
+ ),
+ )
+ .catch(err => {
+ // eslint-disable-next-line no-console
+ console.error(err);
+ process.exit(1);
+ });
diff --git a/docs/src/modules/components/Demo.js b/docs/src/modules/components/Demo.js
index 1ab5fd2fc11c2c..c1242a713a9b6a 100644
--- a/docs/src/modules/components/Demo.js
+++ b/docs/src/modules/components/Demo.js
@@ -15,11 +15,10 @@ import MenuItem from '@material-ui/core/MenuItem';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import Tooltip from '@material-ui/core/Tooltip';
import Github from '@material-ui/docs/svgIcons/GitHub';
-import JSLogo from '@material-ui/docs/svgIcons/JSLogo';
-import HookLogo from '@material-ui/docs/svgIcons/HookLogo';
import MarkdownElement from '@material-ui/docs/MarkdownElement';
-import { getDependencies } from 'docs/src/modules/utils/helpers';
import DemoFrame from 'docs/src/modules/components/DemoFrame';
+import DemoLanguages from 'docs/src/modules/components/DemoLanguages';
+import getDemoConfig from 'docs/src/modules/utils/getDemoConfig';
import { ACTION_TYPES, CODE_VARIANTS } from 'docs/src/modules/constants';
function compress(object) {
@@ -37,55 +36,6 @@ function addHiddenInput(form, name, value) {
form.appendChild(input);
}
-function getDemo(props, raw) {
- const demo = {
- title: 'Material demo',
- description: props.githubLocation,
- dependencies: getDependencies(raw, props.demoOptions.react),
- files: {
- 'demo.js': raw,
- 'index.js': `
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Demo from './demo';
-
-ReactDOM.render( , document.querySelector('#root'));
- `,
- 'index.html': `
-
-
-
-
-
- `,
- },
- };
-
- if (props.codeVariant === CODE_VARIANTS.HOOK) {
- demo.dependencies.react = 'next';
- demo.dependencies['react-dom'] = 'next';
- demo.dependencies['@material-ui/styles'] = 'latest';
- demo.files['index.js'] = `
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Demo from './demo';
-import { createMuiTheme } from "@material-ui/core/styles";
-import { ThemeProvider } from "@material-ui/styles";
-
-const theme = createMuiTheme({ typography: { useNextVariants: true } });
-
-ReactDOM.render(
-
-
- ,
- document.querySelector("#root")
-);
- `;
- }
-
- return demo;
-}
-
const styles = theme => ({
root: {
position: 'relative',
@@ -124,10 +74,10 @@ const styles = theme => ({
[theme.breakpoints.up('sm')]: {
display: 'flex',
flip: false,
- position: 'absolute',
top: 0,
right: theme.spacing.unit,
},
+ justifyContent: 'space-between',
},
code: {
display: 'none',
@@ -160,25 +110,26 @@ class Demo extends React.Component {
};
handleClickCodeSandbox = () => {
- const demo = getDemo(this.props, this.getDemoData().raw);
+ const demoConfig = getDemoConfig(this.getDemoData());
const parameters = compress({
files: {
'package.json': {
content: {
- title: demo.title,
- description: demo.description,
- dependencies: demo.dependencies,
+ title: demoConfig.title,
+ description: demoConfig.description,
+ dependencies: demoConfig.dependencies,
+ devDependencies: {
+ 'react-scripts': 'latest',
+ ...demoConfig.devDependencies,
+ },
+ main: demoConfig.main,
+ scripts: demoConfig.scripts,
},
},
- 'demo.js': {
- content: demo.files['demo.js'],
- },
- 'index.js': {
- content: demo.files['index.js'],
- },
- 'index.html': {
- content: demo.files['index.html'],
- },
+ ...Object.keys(demoConfig.files).reduce((files, name) => {
+ files[name] = { content: demoConfig.files[name] };
+ return files;
+ }, {}),
},
});
@@ -201,8 +152,7 @@ class Demo extends React.Component {
};
handleClickStackBlitz = () => {
- const { codeVariant } = this.state;
- const demo = getDemo(this.props, codeVariant);
+ const demo = getDemoConfig(this.getDemoData());
const form = document.createElement('form');
form.method = 'POST';
form.target = '_blank';
@@ -211,6 +161,7 @@ class Demo extends React.Component {
addHiddenInput(form, 'project[title]', demo.title);
addHiddenInput(form, 'project[description]', demo.description);
addHiddenInput(form, 'project[dependencies]', JSON.stringify(demo.dependencies));
+ addHiddenInput(form, 'project[devDependencies]', JSON.stringify(demo.devDependencies));
Object.keys(demo.files).forEach(key => {
const value = demo.files[key];
addHiddenInput(form, `project[files][${key}]`, value);
@@ -221,9 +172,7 @@ class Demo extends React.Component {
this.handleCloseMore();
};
- handleCodeLanguageClick = event => {
- const codeVariant = event.currentTarget.value;
-
+ handleCodeLanguageClick = (event, codeVariant) => {
if (this.props.codeVariant !== codeVariant) {
document.cookie = `codeVariant=${codeVariant};path=/;max-age=31536000`;
@@ -234,15 +183,6 @@ class Demo extends React.Component {
},
});
}
-
- this.setState(prevState => ({
- /**
- * if the the same code type is open,
- * toggle the state, otherwise if it is
- * another code type always open it. i.e, true
- */
- codeOpen: this.props.codeVariant === codeVariant ? !prevState.codeOpen : true,
- }));
};
handleClickCodeOpen = () => {
@@ -252,137 +192,133 @@ class Demo extends React.Component {
};
getDemoData = () => {
- const { codeVariant, demo } = this.props;
- return codeVariant === CODE_VARIANTS.HOOK && demo.rawHooks
- ? {
- codeVariant: CODE_VARIANTS.HOOK,
- raw: demo.rawHooks,
- js: demo.jsHooks,
- }
- : {
- codeVariant: CODE_VARIANTS.JS,
- js: demo.js,
- raw: demo.raw,
- };
+ const { codeVariant, demo, githubLocation } = this.props;
+ if (codeVariant === CODE_VARIANTS.HOOK && demo.rawHooks) {
+ return {
+ codeVariant: CODE_VARIANTS.HOOK,
+ githubLocation: githubLocation.replace(/\.jsx?$/, '.hooks.js'),
+ raw: demo.rawHooks,
+ js: demo.jsHooks,
+ };
+ }
+ if (codeVariant === CODE_VARIANTS.TS && demo.rawTS) {
+ return {
+ codeVariant: CODE_VARIANTS.TS,
+ githubLocation: githubLocation.replace(/\.js$/, '.tsx'),
+ raw: demo.rawTS,
+ js: demo.js,
+ };
+ }
+
+ return {
+ codeVariant: CODE_VARIANTS.JS,
+ githubLocation,
+ raw: demo.raw,
+ js: demo.js,
+ };
};
render() {
- const { classes, demo, demoOptions, githubLocation: githubLocationJS } = this.props;
+ const { classes, codeVariant, demo, demoOptions } = this.props;
const { anchorEl, codeOpen } = this.state;
const category = demoOptions.demo;
const demoData = this.getDemoData();
const DemoComponent = demoData.js;
- const githubLocation =
- demoData.codeVariant === CODE_VARIANTS.HOOK
- ? githubLocationJS.replace(/\.jsx?$/, '.hooks.js')
- : githubLocationJS;
+ const sourceLanguage = demoData.codeVariant === CODE_VARIANTS.TS ? 'tsx' : 'jsx';
return (
{demoOptions.hideHeader ? null : (
- {demo.rawHooks && (
-
+
+
+
-
+
- )}
- {demo.rawHooks && (
-
+
-
+
- )}
-
-
-
-
-
-
+ {demoOptions.hideEditButton ? null : (
+
+
+
+
+
+ )}
-
+
-
- {demoOptions.hideEditButton ? null : (
-
-
-
-
-
- )}
-
-
-
-
+ {demoOptions.hideEditButton ? null : (
+
+ Edit in StackBlitz (JS only)
+
+ )}
+
+
diff --git a/docs/src/modules/components/DemoLanguages.js b/docs/src/modules/components/DemoLanguages.js
new file mode 100644
index 00000000000000..7ea2b50b1f65f2
--- /dev/null
+++ b/docs/src/modules/components/DemoLanguages.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Fade from '@material-ui/core/Fade';
+import ToggleButton from '@material-ui/lab/ToggleButton';
+import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
+import JavascriptIcon from '@material-ui/docs/svgIcons/Javascript';
+import TypescriptIcon from '@material-ui/docs/svgIcons/Typescript';
+import HooksIcon from '@material-ui/docs/svgIcons/Hooks';
+import { CODE_VARIANTS } from 'docs/src/modules/constants';
+
+function DemoLanguages(props) {
+ const { codeOpen, codeVariant, demo, gaEventCategory, onLanguageClick } = props;
+ const hasHooksVariant = demo.rawHooks;
+ const hasTSVariant = demo.rawTS;
+
+ function renderedCodeVariant() {
+ if (codeVariant === CODE_VARIANTS.TS && hasTSVariant) {
+ return CODE_VARIANTS.TS;
+ }
+ if (codeVariant === CODE_VARIANTS.HOOK && hasHooksVariant) {
+ return CODE_VARIANTS.HOOK;
+ }
+ return CODE_VARIANTS.JS;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+DemoLanguages.propTypes = {
+ codeOpen: PropTypes.bool.isRequired,
+ codeVariant: PropTypes.string.isRequired,
+ demo: PropTypes.object.isRequired,
+ gaEventCategory: PropTypes.string.isRequired,
+ onLanguageClick: PropTypes.func,
+};
+
+export default DemoLanguages;
diff --git a/docs/src/modules/components/MarkdownDocs.js b/docs/src/modules/components/MarkdownDocs.js
index 0913f14d5ab25f..3f358b1c984439 100644
--- a/docs/src/modules/components/MarkdownDocs.js
+++ b/docs/src/modules/components/MarkdownDocs.js
@@ -58,6 +58,7 @@ function MarkdownDocs(props) {
if (req) {
demos = {};
const markdowns = {};
+ const sourceFiles = reqSource.keys();
req.keys().forEach(filename => {
if (filename.indexOf('.md') !== -1) {
const match = filename.match(/-([a-z]{2})\.md$/);
@@ -72,10 +73,20 @@ function MarkdownDocs(props) {
const isHooks = filename.indexOf('.hooks.js') !== -1;
const jsType = isHooks ? 'jsHooks' : 'js';
const rawType = isHooks ? 'rawHooks' : 'raw';
+
+ const tsFilename = !isHooks
+ ? sourceFiles.find(sourceFileName => {
+ const isTSSourceFile = /\.tsx$/.test(sourceFileName);
+ const isTSVersionOfFile = sourceFileName.replace(/\.tsx$/, '.js') === filename;
+ return isTSSourceFile && isTSVersionOfFile;
+ })
+ : undefined;
+
demos[demoName] = {
- ...(demos[demoName] ? demos[demoName] : {}),
+ ...demos[demoName],
[jsType]: req(filename).default,
[rawType]: reqSource(filename),
+ rawTS: tsFilename ? reqSource(tsFilename) : undefined,
};
}
});
diff --git a/docs/src/modules/constants.js b/docs/src/modules/constants.js
index 91115c75898c47..37865f5d85941a 100644
--- a/docs/src/modules/constants.js
+++ b/docs/src/modules/constants.js
@@ -1,5 +1,6 @@
export const CODE_VARIANTS = {
JS: 'JS',
+ TS: 'TS',
HOOK: 'HOOK',
};
diff --git a/docs/src/modules/utils/getDemoConfig.js b/docs/src/modules/utils/getDemoConfig.js
new file mode 100644
index 00000000000000..6c9197a99d3305
--- /dev/null
+++ b/docs/src/modules/utils/getDemoConfig.js
@@ -0,0 +1,111 @@
+import { getDependencies } from './helpers';
+import { CODE_VARIANTS } from 'docs/src/modules/constants';
+
+function jsDemo(demoData) {
+ return {
+ dependencies: getDependencies(demoData.raw),
+ files: {
+ 'demo.js': demoData.raw,
+ 'index.js': `
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Demo from './demo';
+
+ReactDOM.render(
, document.querySelector('#root'));
+ `,
+ },
+ };
+}
+
+function hooksDemo(demoData) {
+ return {
+ dependencies: getDependencies(demoData.rawHooks, { reactVersion: 'next' }),
+ files: {
+ 'index.js': `
+ import React from 'react';
+ import ReactDOM from 'react-dom';
+ import Demo from './demo';
+ import { createMuiTheme } from "@material-ui/core/styles";
+ import { ThemeProvider } from "@material-ui/styles";
+
+ const theme = createMuiTheme({ typography: { useNextVariants: true } });
+
+ ReactDOM.render(
+
+
+ ,
+ document.querySelector("#root")
+ );
+ `,
+ 'demo.js': demoData.raw,
+ },
+ };
+}
+
+function tsDemo(demoData) {
+ return {
+ dependencies: getDependencies(demoData.raw, { codeLanguage: 'TS' }),
+ files: {
+ 'demo.tsx': demoData.raw,
+ 'index.tsx': `
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Demo from './demo';
+
+ReactDOM.render(
, document.querySelector('#root'));
+ `,
+ 'tsconfig.json': `{
+ "compilerOptions": {
+ "module": "esnext",
+ "target": "es5",
+ "lib": ["es6", "dom"],
+ "sourceMap": true,
+ "jsx": "react",
+ "strict": true,
+ "esModuleInterop": true
+ }
+ }`,
+ },
+ main: 'index.tsx',
+ scripts: {
+ start: 'react-scripts start',
+ },
+ };
+}
+
+function getLanguageConfig(demoData) {
+ switch (demoData.codeVariant) {
+ case CODE_VARIANTS.TS:
+ return tsDemo(demoData);
+ case CODE_VARIANTS.HOOKS:
+ return hooksDemo(demoData);
+ default:
+ return jsDemo(demoData);
+ }
+}
+
+export default function getDemo(demoData) {
+ const baseConfig = {
+ title: 'Material demo',
+ description: demoData.githubLocation,
+ files: {
+ 'index.html': `
+
+
+
+
+
+ `,
+ },
+ };
+ const languageConfig = getLanguageConfig(demoData);
+
+ return {
+ ...baseConfig,
+ ...languageConfig,
+ files: {
+ ...baseConfig.files,
+ ...languageConfig.files,
+ },
+ };
+}
diff --git a/docs/src/modules/utils/helpers.js b/docs/src/modules/utils/helpers.js
index 7c8d4004855d82..254cf8f3cda5ce 100644
--- a/docs/src/modules/utils/helpers.js
+++ b/docs/src/modules/utils/helpers.js
@@ -28,7 +28,51 @@ export function pageToTitle(page) {
return titleize(name);
}
-export function getDependencies(raw, reactVersion = 'latest') {
+/**
+ * @var
+ * set of packages that ship their own typings instead of using @types/ namespace
+ * Array because Set([iterable]) is not supported in IE11
+ */
+const packagesWithBundledTypes = ['@material-ui/core', '@material-ui/lab'];
+
+/**
+ * WARNING: Always uses `latest` typings.
+ *
+ * Adds dependencies to @types packages only for packages that are not listed
+ * in packagesWithBundledTypes
+ *
+ * @see packagesWithBundledTypes in this module namespace
+ *
+ * @param {Record
} deps - list of dependency as `name => version`
+ */
+function addTypeDeps(deps) {
+ const packagesWithDTPackage = Object.keys(deps).filter(
+ name => packagesWithBundledTypes.indexOf(name) === -1,
+ );
+
+ packagesWithDTPackage.forEach(name => {
+ let resolvedName = name;
+ // scoped package?
+ if (name.startsWith('@')) {
+ // https://github.com/DefinitelyTyped/DefinitelyTyped#what-about-scoped-packages
+ resolvedName = name.slice(1).replace('/', '__');
+ }
+
+ deps[`@types/${resolvedName}`] = 'latest';
+ });
+
+ return deps;
+}
+
+/**
+ * @param {string} raw - ES6 source with es module imports
+ * @param {objects} options
+ * @param {'JS' | 'TS'} options.codeLanguage
+ * @param {'next' | 'latest'} options.reactVersion
+ * @returns {Record} map of packages with their required version
+ */
+export function getDependencies(raw, options = {}) {
+ const { codeLanguage = 'JS', reactVersion = 'latest' } = options;
const deps = {
'react-dom': reactVersion,
react: reactVersion,
@@ -54,6 +98,11 @@ export function getDependencies(raw, reactVersion = 'latest') {
deps[name] = versions[name] ? versions[name] : 'latest';
}
}
+
+ if (codeLanguage === 'TS') {
+ addTypeDeps(deps);
+ }
+
return deps;
}
diff --git a/docs/src/modules/utils/helpers.test.js b/docs/src/modules/utils/helpers.test.js
index 7e654f497a4348..38ed1e91fd69fb 100644
--- a/docs/src/modules/utils/helpers.test.js
+++ b/docs/src/modules/utils/helpers.test.js
@@ -87,4 +87,18 @@ import { MuiPickersUtilsProvider, TimePicker, DatePicker } from 'material-ui-pic
react: 'latest',
});
});
+
+ it('can collect required @types packages', () => {
+ assert.deepEqual(getDependencies(s1, 'TS'), {
+ '@foo-bar/bip': 'latest',
+ '@material-ui/core': 'latest',
+ 'prop-types': 'latest',
+ 'react-dom': 'latest',
+ react: 'latest',
+ '@types/foo-bar__bip': 'latest',
+ '@types/prop-types': 'latest',
+ '@types/react-dom': 'latest',
+ '@types/react': 'latest',
+ });
+ });
});
diff --git a/docs/src/pages/demos/app-bar/BottomAppBar.tsx b/docs/src/pages/demos/app-bar/BottomAppBar.tsx
new file mode 100644
index 00000000000000..f2b34cc86143ff
--- /dev/null
+++ b/docs/src/pages/demos/app-bar/BottomAppBar.tsx
@@ -0,0 +1,154 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
+import AppBar from '@material-ui/core/AppBar';
+import CssBaseline from '@material-ui/core/CssBaseline';
+import Toolbar from '@material-ui/core/Toolbar';
+import Typography from '@material-ui/core/Typography';
+import IconButton from '@material-ui/core/IconButton';
+import Paper from '@material-ui/core/Paper';
+import Fab from '@material-ui/core/Fab';
+import List from '@material-ui/core/List';
+import ListItem from '@material-ui/core/ListItem';
+import ListItemText from '@material-ui/core/ListItemText';
+import ListSubheader from '@material-ui/core/ListSubheader';
+import Avatar from '@material-ui/core/Avatar';
+import MenuIcon from '@material-ui/icons/Menu';
+import AddIcon from '@material-ui/icons/Add';
+import SearchIcon from '@material-ui/icons/Search';
+import MoreIcon from '@material-ui/icons/MoreVert';
+
+const styles = (theme: Theme) =>
+ createStyles({
+ text: {
+ paddingTop: theme.spacing.unit * 2,
+ paddingLeft: theme.spacing.unit * 2,
+ paddingRight: theme.spacing.unit * 2,
+ },
+ paper: {
+ paddingBottom: 50,
+ },
+ list: {
+ marginBottom: theme.spacing.unit * 2,
+ },
+ subHeader: {
+ backgroundColor: theme.palette.background.paper,
+ },
+ appBar: {
+ top: 'auto',
+ bottom: 0,
+ },
+ toolbar: {
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ fabButton: {
+ position: 'absolute',
+ zIndex: 1,
+ top: -30,
+ left: 0,
+ right: 0,
+ margin: '0 auto',
+ },
+ });
+
+const messages = [
+ {
+ id: 1,
+ primary: 'Brunch this week?',
+ secondary: "I'll be in the neighbourhood this week. Let's grab a bite to eat",
+ person: '/static/images/avatar/5.jpg',
+ },
+ {
+ id: 2,
+ primary: 'Birthday Gift',
+ secondary: `Do you have a suggestion for a good present for John on his work
+ anniversary. I am really confused & would love your thoughts on it.`,
+ person: '/static/images/avatar/1.jpg',
+ },
+ {
+ id: 3,
+ primary: 'Recipe to try',
+ secondary: 'I am try out this new BBQ recipe, I think this might be amazing',
+ person: '/static/images/avatar/2.jpg',
+ },
+ {
+ id: 4,
+ primary: 'Yes!',
+ secondary: 'I have the tickets to the ReactConf for this year.',
+ person: '/static/images/avatar/3.jpg',
+ },
+ {
+ id: 5,
+ primary: "Doctor's Appointment",
+ secondary: 'My appointment for the doctor was rescheduled for next Saturday.',
+ person: '/static/images/avatar/4.jpg',
+ },
+ {
+ id: 6,
+ primary: 'Discussion',
+ secondary: `Menus that are generated by the bottom app bar (such as a bottom
+ navigation drawer or overflow menu) open as bottom sheets at a higher elevation
+ than the bar.`,
+ person: '/static/images/avatar/5.jpg',
+ },
+ {
+ id: 7,
+ primary: 'Summer BBQ',
+ secondary: `Who wants to have a cookout this weekend? I just got some furniture
+ for my backyard and would love to fire up the grill.`,
+ person: '/static/images/avatar/1.jpg',
+ },
+];
+
+export interface Props extends WithStyles {}
+
+function BottomAppBar(props: Props) {
+ const { classes } = props;
+ return (
+
+
+
+
+ Inbox
+
+
+ {messages.map(({ id, primary, secondary, person }) => (
+
+ {id === 1 && Today }
+ {id === 3 && Yesterday }
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+BottomAppBar.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(BottomAppBar);
diff --git a/docs/src/pages/demos/app-bar/ButtonAppBar.tsx b/docs/src/pages/demos/app-bar/ButtonAppBar.tsx
new file mode 100644
index 00000000000000..f8baad446bb5a9
--- /dev/null
+++ b/docs/src/pages/demos/app-bar/ButtonAppBar.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import Typography from '@material-ui/core/Typography';
+import Button from '@material-ui/core/Button';
+import IconButton from '@material-ui/core/IconButton';
+import MenuIcon from '@material-ui/icons/Menu';
+
+const styles = createStyles({
+ root: {
+ flexGrow: 1,
+ },
+ grow: {
+ flexGrow: 1,
+ },
+ menuButton: {
+ marginLeft: -12,
+ marginRight: 20,
+ },
+});
+
+export interface Props extends WithStyles {}
+
+function ButtonAppBar(props: Props) {
+ const { classes } = props;
+ return (
+
+
+
+
+
+
+
+ News
+
+ Login
+
+
+
+ );
+}
+
+ButtonAppBar.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(ButtonAppBar);
diff --git a/docs/src/pages/demos/app-bar/DenseAppBar.tsx b/docs/src/pages/demos/app-bar/DenseAppBar.tsx
new file mode 100644
index 00000000000000..0709fd96f6cc6c
--- /dev/null
+++ b/docs/src/pages/demos/app-bar/DenseAppBar.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import Typography from '@material-ui/core/Typography';
+import IconButton from '@material-ui/core/IconButton';
+import MenuIcon from '@material-ui/icons/Menu';
+
+const styles = createStyles({
+ root: {
+ flexGrow: 1,
+ },
+ menuButton: {
+ marginLeft: -18,
+ marginRight: 10,
+ },
+});
+
+export interface Props extends WithStyles {}
+
+function DenseAppBar(props: Props) {
+ const { classes } = props;
+ return (
+
+
+
+
+
+
+
+ Photos
+
+
+
+
+ );
+}
+
+DenseAppBar.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(DenseAppBar);
diff --git a/docs/src/pages/demos/app-bar/MenuAppBar.tsx b/docs/src/pages/demos/app-bar/MenuAppBar.tsx
new file mode 100644
index 00000000000000..6036d28fb13eaa
--- /dev/null
+++ b/docs/src/pages/demos/app-bar/MenuAppBar.tsx
@@ -0,0 +1,117 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { createStyles, withStyles, WithStyles } from '@material-ui/core/styles';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import Typography from '@material-ui/core/Typography';
+import IconButton from '@material-ui/core/IconButton';
+import MenuIcon from '@material-ui/icons/Menu';
+import AccountCircle from '@material-ui/icons/AccountCircle';
+import Switch from '@material-ui/core/Switch';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import FormGroup from '@material-ui/core/FormGroup';
+import MenuItem from '@material-ui/core/MenuItem';
+import Menu from '@material-ui/core/Menu';
+
+const styles = createStyles({
+ root: {
+ flexGrow: 1,
+ },
+ grow: {
+ flexGrow: 1,
+ },
+ menuButton: {
+ marginLeft: -12,
+ marginRight: 20,
+ },
+});
+
+export interface Props extends WithStyles {}
+
+export interface State {
+ auth: boolean;
+ anchorEl: null | HTMLElement;
+}
+
+class MenuAppBar extends React.Component {
+ state: State = {
+ auth: true,
+ anchorEl: null,
+ };
+
+ handleChange = (event: React.ChangeEvent) => {
+ this.setState({ auth: event.target.checked });
+ };
+
+ handleMenu = (event: React.MouseEvent) => {
+ this.setState({ anchorEl: event.currentTarget });
+ };
+
+ handleClose = () => {
+ this.setState({ anchorEl: null });
+ };
+
+ render() {
+ const { classes } = this.props;
+ const { auth, anchorEl } = this.state;
+ const open = Boolean(anchorEl);
+
+ return (
+
+
+
+ }
+ label={auth ? 'Logout' : 'Login'}
+ />
+
+
+
+
+
+
+
+ Photos
+
+ {auth && (
+
+ )}
+
+
+
+ );
+ }
+}
+
+(MenuAppBar as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(MenuAppBar);
diff --git a/docs/src/pages/demos/app-bar/PrimarySearchAppBar.js b/docs/src/pages/demos/app-bar/PrimarySearchAppBar.js
index 59f612fd6229e8..936edacf13aba5 100644
--- a/docs/src/pages/demos/app-bar/PrimarySearchAppBar.js
+++ b/docs/src/pages/demos/app-bar/PrimarySearchAppBar.js
@@ -135,9 +135,9 @@ class PrimarySearchAppBar extends React.Component {
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={isMobileMenuOpen}
- onClose={this.handleMobileMenuClose}
+ onClose={this.handleMenuClose}
>
-
+
@@ -145,7 +145,7 @@ class PrimarySearchAppBar extends React.Component {
Messages
-
+
diff --git a/docs/src/pages/demos/app-bar/PrimarySearchAppBar.tsx b/docs/src/pages/demos/app-bar/PrimarySearchAppBar.tsx
new file mode 100644
index 00000000000000..56ee618b011979
--- /dev/null
+++ b/docs/src/pages/demos/app-bar/PrimarySearchAppBar.tsx
@@ -0,0 +1,234 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import IconButton from '@material-ui/core/IconButton';
+import Typography from '@material-ui/core/Typography';
+import InputBase from '@material-ui/core/InputBase';
+import Badge from '@material-ui/core/Badge';
+import MenuItem from '@material-ui/core/MenuItem';
+import Menu from '@material-ui/core/Menu';
+import { fade } from '@material-ui/core/styles/colorManipulator';
+import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
+import MenuIcon from '@material-ui/icons/Menu';
+import SearchIcon from '@material-ui/icons/Search';
+import AccountCircle from '@material-ui/icons/AccountCircle';
+import MailIcon from '@material-ui/icons/Mail';
+import NotificationsIcon from '@material-ui/icons/Notifications';
+import MoreIcon from '@material-ui/icons/MoreVert';
+
+const styles = (theme: Theme) =>
+ createStyles({
+ root: {
+ width: '100%',
+ },
+ grow: {
+ flexGrow: 1,
+ },
+ menuButton: {
+ marginLeft: -12,
+ marginRight: 20,
+ },
+ title: {
+ display: 'none',
+ [theme.breakpoints.up('sm')]: {
+ display: 'block',
+ },
+ },
+ search: {
+ position: 'relative',
+ borderRadius: theme.shape.borderRadius,
+ backgroundColor: fade(theme.palette.common.white, 0.15),
+ '&:hover': {
+ backgroundColor: fade(theme.palette.common.white, 0.25),
+ },
+ marginRight: theme.spacing.unit * 2,
+ marginLeft: 0,
+ width: '100%',
+ [theme.breakpoints.up('sm')]: {
+ marginLeft: theme.spacing.unit * 3,
+ width: 'auto',
+ },
+ },
+ searchIcon: {
+ width: theme.spacing.unit * 9,
+ height: '100%',
+ position: 'absolute',
+ pointerEvents: 'none',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ inputRoot: {
+ color: 'inherit',
+ width: '100%',
+ },
+ inputInput: {
+ paddingTop: theme.spacing.unit,
+ paddingRight: theme.spacing.unit,
+ paddingBottom: theme.spacing.unit,
+ paddingLeft: theme.spacing.unit * 10,
+ transition: theme.transitions.create('width'),
+ width: '100%',
+ [theme.breakpoints.up('md')]: {
+ width: 200,
+ },
+ },
+ sectionDesktop: {
+ display: 'none',
+ [theme.breakpoints.up('md')]: {
+ display: 'flex',
+ },
+ },
+ sectionMobile: {
+ display: 'flex',
+ [theme.breakpoints.up('md')]: {
+ display: 'none',
+ },
+ },
+ });
+
+export interface Props extends WithStyles {}
+
+interface State {
+ anchorEl: null | HTMLElement;
+ mobileMoreAnchorEl: null | HTMLElement;
+}
+
+class PrimarySearchAppBar extends React.Component {
+ state: State = {
+ anchorEl: null,
+ mobileMoreAnchorEl: null,
+ };
+
+ handleProfileMenuOpen = (event: React.MouseEvent) => {
+ this.setState({ anchorEl: event.currentTarget });
+ };
+
+ handleMenuClose = () => {
+ this.setState({ anchorEl: null });
+ this.handleMobileMenuClose();
+ };
+
+ handleMobileMenuOpen = (event: React.MouseEvent) => {
+ this.setState({ mobileMoreAnchorEl: event.currentTarget });
+ };
+
+ handleMobileMenuClose = () => {
+ this.setState({ mobileMoreAnchorEl: null });
+ };
+
+ render() {
+ const { anchorEl, mobileMoreAnchorEl } = this.state;
+ const { classes } = this.props;
+ const isMenuOpen = Boolean(anchorEl);
+ const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
+
+ const renderMenu = (
+
+ Profile
+ My account
+
+ );
+
+ const renderMobileMenu = (
+
+
+
+
+
+
+
+ Messages
+
+
+
+
+
+
+
+ Notifications
+
+
+
+
+
+ Profile
+
+
+ );
+
+ return (
+
+
+
+
+
+
+
+ Material-UI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderMenu}
+ {renderMobileMenu}
+
+ );
+ }
+}
+
+(PrimarySearchAppBar as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(PrimarySearchAppBar);
diff --git a/docs/src/pages/demos/app-bar/SearchAppBar.tsx b/docs/src/pages/demos/app-bar/SearchAppBar.tsx
new file mode 100644
index 00000000000000..488d6d57c8c22a
--- /dev/null
+++ b/docs/src/pages/demos/app-bar/SearchAppBar.tsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import IconButton from '@material-ui/core/IconButton';
+import Typography from '@material-ui/core/Typography';
+import InputBase from '@material-ui/core/InputBase';
+import { fade } from '@material-ui/core/styles/colorManipulator';
+import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
+import MenuIcon from '@material-ui/icons/Menu';
+import SearchIcon from '@material-ui/icons/Search';
+
+const styles = (theme: Theme) =>
+ createStyles({
+ root: {
+ width: '100%',
+ },
+ grow: {
+ flexGrow: 1,
+ },
+ menuButton: {
+ marginLeft: -12,
+ marginRight: 20,
+ },
+ title: {
+ display: 'none',
+ [theme.breakpoints.up('sm')]: {
+ display: 'block',
+ },
+ },
+ search: {
+ position: 'relative',
+ borderRadius: theme.shape.borderRadius,
+ backgroundColor: fade(theme.palette.common.white, 0.15),
+ '&:hover': {
+ backgroundColor: fade(theme.palette.common.white, 0.25),
+ },
+ marginLeft: 0,
+ width: '100%',
+ [theme.breakpoints.up('sm')]: {
+ marginLeft: theme.spacing.unit,
+ width: 'auto',
+ },
+ },
+ searchIcon: {
+ width: theme.spacing.unit * 9,
+ height: '100%',
+ position: 'absolute',
+ pointerEvents: 'none',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ inputRoot: {
+ color: 'inherit',
+ width: '100%',
+ },
+ inputInput: {
+ paddingTop: theme.spacing.unit,
+ paddingRight: theme.spacing.unit,
+ paddingBottom: theme.spacing.unit,
+ paddingLeft: theme.spacing.unit * 10,
+ transition: theme.transitions.create('width'),
+ width: '100%',
+ [theme.breakpoints.up('sm')]: {
+ width: 120,
+ '&:focus': {
+ width: 200,
+ },
+ },
+ },
+ });
+
+export interface Props extends WithStyles {}
+
+function SearchAppBar(props: Props) {
+ const { classes } = props;
+ return (
+
+
+
+
+
+
+
+ Material-UI
+
+
+
+
+
+
+ );
+}
+
+SearchAppBar.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(SearchAppBar);
diff --git a/docs/src/pages/demos/app-bar/SimpleAppBar.tsx b/docs/src/pages/demos/app-bar/SimpleAppBar.tsx
new file mode 100644
index 00000000000000..b181e24de5a743
--- /dev/null
+++ b/docs/src/pages/demos/app-bar/SimpleAppBar.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withStyles, WithStyles } from '@material-ui/core/styles';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import Typography from '@material-ui/core/Typography';
+
+const styles = {
+ root: {
+ flexGrow: 1,
+ },
+};
+
+export interface Props extends WithStyles {}
+
+function SimpleAppBar(props: Props) {
+ const { classes } = props;
+
+ return (
+
+ );
+}
+
+SimpleAppBar.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(SimpleAppBar);
diff --git a/docs/src/pages/demos/text-fields/ComposedTextField.js b/docs/src/pages/demos/text-fields/ComposedTextField.js
index 5402e509f500a5..4264026a145469 100644
--- a/docs/src/pages/demos/text-fields/ComposedTextField.js
+++ b/docs/src/pages/demos/text-fields/ComposedTextField.js
@@ -49,6 +49,7 @@ class ComposedTextField extends React.Component {
onChange={this.handleChange}
aria-describedby="component-helper-text"
/>
+
Some important helper text
@@ -64,6 +65,7 @@ class ComposedTextField extends React.Component {
onChange={this.handleChange}
aria-describedby="component-error-text"
/>
+
Error
diff --git a/docs/src/pages/demos/text-fields/ComposedTextField.tsx b/docs/src/pages/demos/text-fields/ComposedTextField.tsx
new file mode 100644
index 00000000000000..b49745de704cc9
--- /dev/null
+++ b/docs/src/pages/demos/text-fields/ComposedTextField.tsx
@@ -0,0 +1,107 @@
+import React, { ComponentClass } from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
+import FilledInput from '@material-ui/core/FilledInput';
+import FormControl from '@material-ui/core/FormControl';
+import FormHelperText from '@material-ui/core/FormHelperText';
+import Input from '@material-ui/core/Input';
+import InputLabel from '@material-ui/core/InputLabel';
+import OutlinedInput from '@material-ui/core/OutlinedInput';
+
+const styles = (theme: Theme) =>
+ createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ formControl: {
+ margin: theme.spacing.unit,
+ },
+ });
+
+export interface Props extends WithStyles {}
+
+interface State {
+ name: string;
+}
+
+class ComposedTextField extends React.Component {
+ labelRef: HTMLElement | null | undefined;
+
+ state = {
+ name: 'Composed TextField',
+ };
+
+ componentDidMount() {
+ this.forceUpdate();
+ }
+
+ handleChange = (event: React.ChangeEvent) => {
+ this.setState({ name: event.target.value });
+ };
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+
+ Name
+
+
+
+ Name
+
+ Some important helper text
+
+
+ Name
+
+ Disabled
+
+
+ Name
+
+ Error
+
+
+ {
+ this.labelRef = ReactDOM.findDOMNode(ref!) as HTMLLabelElement | null;
+ }}
+ htmlFor="component-outlined"
+ >
+ Name
+
+
+
+
+ Name
+
+
+
+ );
+ }
+}
+
+(ComposedTextField as ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(ComposedTextField);
diff --git a/docs/src/pages/demos/text-fields/CustomizedInputs.js b/docs/src/pages/demos/text-fields/CustomizedInputs.js
index 4e50373b804f5a..fa01bd82148575 100644
--- a/docs/src/pages/demos/text-fields/CustomizedInputs.js
+++ b/docs/src/pages/demos/text-fields/CustomizedInputs.js
@@ -120,12 +120,14 @@ function CustomizedInputs(props) {
variant="outlined"
id="custom-css-outlined-input"
/>
+
+
+ createStyles({
+ root: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ margin: {
+ margin: theme.spacing.unit,
+ },
+ cssLabel: {
+ '&$cssFocused': {
+ color: purple[500],
+ },
+ },
+ cssFocused: {},
+ cssUnderline: {
+ '&:after': {
+ borderBottomColor: purple[500],
+ },
+ },
+ cssOutlinedInput: {
+ '&$cssFocused $notchedOutline': {
+ borderColor: purple[500],
+ },
+ },
+ notchedOutline: {},
+ bootstrapRoot: {
+ 'label + &': {
+ marginTop: theme.spacing.unit * 3,
+ },
+ },
+ bootstrapInput: {
+ borderRadius: 4,
+ position: 'relative',
+ backgroundColor: theme.palette.common.white,
+ border: '1px solid #ced4da',
+ fontSize: 16,
+ width: 'auto',
+ padding: '10px 12px',
+ transition: theme.transitions.create(['border-color', 'box-shadow']),
+ // Use the system font instead of the default Roboto font.
+ fontFamily: [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ '"Segoe UI"',
+ 'Roboto',
+ '"Helvetica Neue"',
+ 'Arial',
+ 'sans-serif',
+ '"Apple Color Emoji"',
+ '"Segoe UI Emoji"',
+ '"Segoe UI Symbol"',
+ ].join(','),
+ '&:focus': {
+ borderRadius: 4,
+ borderColor: '#80bdff',
+ boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
+ },
+ },
+ bootstrapFormLabel: {
+ fontSize: 18,
+ },
+ });
+
+const theme = createMuiTheme({
+ palette: {
+ primary: green,
+ },
+ typography: { useNextVariants: true },
+});
+
+export interface Props extends WithStyles {}
+
+function CustomizedInputs(props: Props) {
+ const { classes } = props;
+
+ return (
+
+
+
+ Custom CSS
+
+
+
+
+
+
+
+
+
+
+ Bootstrap
+
+
+
+
+
+ );
+}
+
+CustomizedInputs.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(CustomizedInputs);
diff --git a/docs/src/pages/demos/text-fields/FilledInputAdornments.js b/docs/src/pages/demos/text-fields/FilledInputAdornments.js
index b2b6a59a0308db..9d137dc1e072fe 100644
--- a/docs/src/pages/demos/text-fields/FilledInputAdornments.js
+++ b/docs/src/pages/demos/text-fields/FilledInputAdornments.js
@@ -68,6 +68,7 @@ class FilledInputAdornments extends React.Component {
startAdornment: Kg ,
}}
/>
+
$,
}}
/>
+
Kg,
}}
/>
+
+ createStyles({
+ root: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ margin: {
+ margin: theme.spacing.unit,
+ },
+ textField: {
+ flexBasis: 200,
+ },
+ });
+
+const ranges = [
+ {
+ value: '0-20',
+ label: '0 to 20',
+ },
+ {
+ value: '21-50',
+ label: '21 to 50',
+ },
+ {
+ value: '51-100',
+ label: '51 to 100',
+ },
+];
+
+export interface Props extends WithStyles {}
+
+interface State {
+ amount: string;
+ password: string;
+ weight: string;
+ weightRange: string;
+ showPassword: boolean;
+}
+
+class FilledInputAdornments extends React.Component {
+ state = {
+ amount: '',
+ password: '',
+ weight: '',
+ weightRange: '',
+ showPassword: false,
+ };
+
+ handleChange = (prop: 'amount' | 'password' | 'weight' | 'weightRange') => (
+ event: React.ChangeEvent,
+ ) => {
+ this.setState({ [prop]: event.target.value } as Pick);
+ };
+
+ handleClickShowPassword = () => {
+ this.setState(state => ({ showPassword: !state.showPassword }));
+ };
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+ Kg,
+ }}
+ />
+ Kg,
+ }}
+ >
+ {ranges.map(option => (
+
+ {option.label}
+
+ ))}
+
+ $,
+ }}
+ />
+ Kg,
+ }}
+ />
+
+
+ {this.state.showPassword ? : }
+
+
+ ),
+ }}
+ />
+
+ );
+ }
+}
+
+(FilledInputAdornments as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(FilledInputAdornments);
diff --git a/docs/src/pages/demos/text-fields/FilledTextFields.js b/docs/src/pages/demos/text-fields/FilledTextFields.js
index 100b821f9d4d0f..0189030bd5cddf 100644
--- a/docs/src/pages/demos/text-fields/FilledTextFields.js
+++ b/docs/src/pages/demos/text-fields/FilledTextFields.js
@@ -69,6 +69,7 @@ class FilledTextFields extends React.Component {
margin="normal"
variant="filled"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ textField: {
+ marginLeft: theme.spacing.unit,
+ marginRight: theme.spacing.unit,
+ },
+ dense: {
+ marginTop: 16,
+ },
+ menu: {
+ width: 200,
+ },
+ });
+
+const currencies = [
+ {
+ value: 'USD',
+ label: '$',
+ },
+ {
+ value: 'EUR',
+ label: '€',
+ },
+ {
+ value: 'BTC',
+ label: '฿',
+ },
+ {
+ value: 'JPY',
+ label: '¥',
+ },
+];
+
+export interface Props extends WithStyles {}
+
+interface State {
+ name: string;
+ age: string;
+ multiline: string;
+ currency: string;
+}
+
+class FilledTextFields extends React.Component {
+ state = {
+ name: 'Cat in the Hat',
+ age: '',
+ multiline: 'Controlled',
+ currency: 'EUR',
+ };
+
+ handleChange = (name: keyof State) => (event: React.ChangeEvent) => {
+ this.setState({
+ [name]: event.target.value,
+ } as Pick);
+ };
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+(FilledTextFields as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(FilledTextFields);
diff --git a/docs/src/pages/demos/text-fields/FormattedInputs.tsx b/docs/src/pages/demos/text-fields/FormattedInputs.tsx
new file mode 100644
index 00000000000000..e1bc3f7ca7bf0f
--- /dev/null
+++ b/docs/src/pages/demos/text-fields/FormattedInputs.tsx
@@ -0,0 +1,129 @@
+import React from 'react';
+import MaskedInput from 'react-text-mask';
+import NumberFormat from 'react-number-format';
+import PropTypes from 'prop-types';
+import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
+import Input from '@material-ui/core/Input';
+import InputLabel from '@material-ui/core/InputLabel';
+import TextField from '@material-ui/core/TextField';
+import FormControl from '@material-ui/core/FormControl';
+
+const styles = (theme: Theme) =>
+ createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ formControl: {
+ margin: theme.spacing.unit,
+ },
+ });
+
+export interface TextMaskCustomProps {
+ inputRef: (ref: HTMLInputElement | null) => void;
+}
+
+function TextMaskCustom(props: TextMaskCustomProps) {
+ const { inputRef, ...other } = props;
+
+ return (
+ {
+ inputRef(ref ? ref.inputElement : null);
+ }}
+ mask={['(', /[1-9]/, /\d/, /\d/, ')', ' ', /\d/, /\d/, /\d/, '-', /\d/, /\d/, /\d/, /\d/]}
+ placeholderChar={'\u2000'}
+ showMask
+ />
+ );
+}
+
+TextMaskCustom.propTypes = {
+ inputRef: PropTypes.func.isRequired,
+} as any;
+
+export interface NumberFormatCustomProps {
+ inputRef: (instance: NumberFormat | null) => void;
+ onChange: (event: { target: { value: string } }) => void;
+}
+
+function NumberFormatCustom(props: NumberFormatCustomProps) {
+ const { inputRef, onChange, ...other } = props;
+
+ return (
+ {
+ onChange({
+ target: {
+ value: values.value,
+ },
+ });
+ }}
+ thousandSeparator
+ prefix="$"
+ />
+ );
+}
+
+NumberFormatCustom.propTypes = {
+ inputRef: PropTypes.func.isRequired,
+ onChange: PropTypes.func.isRequired,
+} as any;
+
+export interface Props extends WithStyles {}
+
+interface State {
+ textmask: string;
+ numberformat: string;
+}
+
+class FormattedInputs extends React.Component {
+ state = {
+ textmask: '(1 ) - ',
+ numberformat: '1320',
+ };
+
+ handleChange = (name: keyof State) => (event: React.ChangeEvent) => {
+ this.setState({
+ [name]: event.target.value,
+ } as Pick);
+ };
+
+ render() {
+ const { classes } = this.props;
+ const { textmask, numberformat } = this.state;
+
+ return (
+
+
+ react-text-mask
+
+
+
+
+ );
+ }
+}
+
+(FormattedInputs as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(FormattedInputs);
diff --git a/docs/src/pages/demos/text-fields/InputAdornments.js b/docs/src/pages/demos/text-fields/InputAdornments.js
index 01714dfff66d22..cdd1f88103d158 100644
--- a/docs/src/pages/demos/text-fields/InputAdornments.js
+++ b/docs/src/pages/demos/text-fields/InputAdornments.js
@@ -74,6 +74,7 @@ class InputAdornments extends React.Component {
startAdornment: Kg ,
}}
/>
+
+
Weight
diff --git a/docs/src/pages/demos/text-fields/InputAdornments.tsx b/docs/src/pages/demos/text-fields/InputAdornments.tsx
new file mode 100644
index 00000000000000..4d409e25abb3fe
--- /dev/null
+++ b/docs/src/pages/demos/text-fields/InputAdornments.tsx
@@ -0,0 +1,158 @@
+import React from 'react';
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
+import IconButton from '@material-ui/core/IconButton';
+import Input from '@material-ui/core/Input';
+import InputLabel from '@material-ui/core/InputLabel';
+import InputAdornment from '@material-ui/core/InputAdornment';
+import FormHelperText from '@material-ui/core/FormHelperText';
+import FormControl from '@material-ui/core/FormControl';
+import TextField from '@material-ui/core/TextField';
+import MenuItem from '@material-ui/core/MenuItem';
+import Visibility from '@material-ui/icons/Visibility';
+import VisibilityOff from '@material-ui/icons/VisibilityOff';
+
+const styles = (theme: Theme) =>
+ createStyles({
+ root: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ margin: {
+ margin: theme.spacing.unit,
+ },
+ withoutLabel: {
+ marginTop: theme.spacing.unit * 3,
+ },
+ textField: {
+ flexBasis: 200,
+ },
+ });
+
+const ranges = [
+ {
+ value: '0-20',
+ label: '0 to 20',
+ },
+ {
+ value: '21-50',
+ label: '21 to 50',
+ },
+ {
+ value: '51-100',
+ label: '51 to 100',
+ },
+];
+
+export interface Props extends WithStyles {}
+
+interface State {
+ amount: string;
+ password: string;
+ weight: string;
+ weightRange: string;
+ showPassword: boolean;
+}
+
+class InputAdornments extends React.Component {
+ state = {
+ amount: '',
+ password: '',
+ weight: '',
+ weightRange: '',
+ showPassword: false,
+ };
+
+ handleChange = (prop: 'amount' | 'password' | 'weight' | 'weightRange') => (
+ event: React.ChangeEvent,
+ ) => {
+ this.setState({ [prop]: event.target.value } as Pick);
+ };
+
+ handleClickShowPassword = () => {
+ this.setState(state => ({ showPassword: !state.showPassword }));
+ };
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+ Kg,
+ }}
+ />
+ Kg,
+ }}
+ >
+ {ranges.map(option => (
+
+ {option.label}
+
+ ))}
+
+
+ Amount
+ $}
+ />
+
+
+ Kg}
+ inputProps={{
+ 'aria-label': 'Weight',
+ }}
+ />
+ Weight
+
+
+ Password
+
+
+ {this.state.showPassword ? : }
+
+
+ }
+ />
+
+
+ );
+ }
+}
+
+(InputAdornments as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(InputAdornments);
diff --git a/docs/src/pages/demos/text-fields/InputWithIcon.js b/docs/src/pages/demos/text-fields/InputWithIcon.js
index b59e8413f9bac0..d96bcabc23e79c 100644
--- a/docs/src/pages/demos/text-fields/InputWithIcon.js
+++ b/docs/src/pages/demos/text-fields/InputWithIcon.js
@@ -43,6 +43,7 @@ function InputWithIcon(props) {
),
}}
/>
+
diff --git a/docs/src/pages/demos/text-fields/InputWithIcon.tsx b/docs/src/pages/demos/text-fields/InputWithIcon.tsx
new file mode 100644
index 00000000000000..a3eeae0a7d4130
--- /dev/null
+++ b/docs/src/pages/demos/text-fields/InputWithIcon.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { createStyles, Theme, withStyles, WithStyles } from '@material-ui/core/styles';
+import Input from '@material-ui/core/Input';
+import InputLabel from '@material-ui/core/InputLabel';
+import InputAdornment from '@material-ui/core/InputAdornment';
+import FormControl from '@material-ui/core/FormControl';
+import TextField from '@material-ui/core/TextField';
+import Grid from '@material-ui/core/Grid';
+import AccountCircle from '@material-ui/icons/AccountCircle';
+
+const styles = (theme: Theme) =>
+ createStyles({
+ margin: {
+ margin: theme.spacing.unit,
+ },
+ });
+
+export interface Props extends WithStyles {}
+
+function InputWithIcon(props: Props) {
+ const { classes } = props;
+
+ return (
+
+ );
+}
+
+InputWithIcon.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(InputWithIcon);
diff --git a/docs/src/pages/demos/text-fields/Inputs.js b/docs/src/pages/demos/text-fields/Inputs.js
index ba6f496c909dca..03b0eba7272b6b 100644
--- a/docs/src/pages/demos/text-fields/Inputs.js
+++ b/docs/src/pages/demos/text-fields/Inputs.js
@@ -24,6 +24,7 @@ function Inputs(props) {
'aria-label': 'Description',
}}
/>
+
+
+
+ createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ input: {
+ margin: theme.spacing.unit,
+ },
+ });
+
+export interface Props extends WithStyles {}
+
+function Inputs(props: Props) {
+ const { classes } = props;
+ return (
+
+
+
+
+
+
+ );
+}
+
+Inputs.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(Inputs);
diff --git a/docs/src/pages/demos/text-fields/OutlinedInputAdornments.js b/docs/src/pages/demos/text-fields/OutlinedInputAdornments.js
index 82fe328588282d..9918a5776137df 100644
--- a/docs/src/pages/demos/text-fields/OutlinedInputAdornments.js
+++ b/docs/src/pages/demos/text-fields/OutlinedInputAdornments.js
@@ -68,6 +68,7 @@ class OutlinedInputAdornments extends React.Component {
startAdornment: Kg ,
}}
/>
+
$,
}}
/>
+
Kg,
}}
/>
+
+ createStyles({
+ root: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ margin: {
+ margin: theme.spacing.unit,
+ },
+ textField: {
+ flexBasis: 200,
+ },
+ });
+
+const ranges = [
+ {
+ value: '0-20',
+ label: '0 to 20',
+ },
+ {
+ value: '21-50',
+ label: '21 to 50',
+ },
+ {
+ value: '51-100',
+ label: '51 to 100',
+ },
+];
+
+export interface Props extends WithStyles {}
+
+interface State {
+ amount: string;
+ password: string;
+ weight: string;
+ weightRange: string;
+ showPassword: boolean;
+}
+
+class OutlinedInputAdornments extends React.Component {
+ state = {
+ amount: '',
+ password: '',
+ weight: '',
+ weightRange: '',
+ showPassword: false,
+ };
+
+ handleChange = (prop: 'amount' | 'password' | 'weight' | 'weightRange') => (
+ event: React.ChangeEvent,
+ ) => {
+ this.setState({ [prop]: event.target.value } as Pick);
+ };
+
+ handleClickShowPassword = () => {
+ this.setState(state => ({ showPassword: !state.showPassword }));
+ };
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+ Kg,
+ }}
+ />
+ Kg,
+ }}
+ >
+ {ranges.map(option => (
+
+ {option.label}
+
+ ))}
+
+ $,
+ }}
+ />
+ Kg,
+ }}
+ />
+
+
+ {this.state.showPassword ? : }
+
+
+ ),
+ }}
+ />
+
+ );
+ }
+}
+
+(OutlinedInputAdornments as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(OutlinedInputAdornments);
diff --git a/docs/src/pages/demos/text-fields/OutlinedTextFields.js b/docs/src/pages/demos/text-fields/OutlinedTextFields.js
index f742e199bde162..6b73fbe6816767 100644
--- a/docs/src/pages/demos/text-fields/OutlinedTextFields.js
+++ b/docs/src/pages/demos/text-fields/OutlinedTextFields.js
@@ -69,6 +69,7 @@ class OutlinedTextFields extends React.Component {
margin="normal"
variant="outlined"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ textField: {
+ marginLeft: theme.spacing.unit,
+ marginRight: theme.spacing.unit,
+ },
+ dense: {
+ marginTop: 16,
+ },
+ menu: {
+ width: 200,
+ },
+ });
+
+const currencies = [
+ {
+ value: 'USD',
+ label: '$',
+ },
+ {
+ value: 'EUR',
+ label: '€',
+ },
+ {
+ value: 'BTC',
+ label: '฿',
+ },
+ {
+ value: 'JPY',
+ label: '¥',
+ },
+];
+
+export interface Props extends WithStyles {}
+
+interface State {
+ name: string;
+ age: string;
+ multiline: string;
+ currency: string;
+}
+
+class OutlinedTextFields extends React.Component {
+ state = {
+ name: 'Cat in the Hat',
+ age: '',
+ multiline: 'Controlled',
+ currency: 'EUR',
+ };
+
+ handleChange = (name: keyof State) => (event: React.ChangeEvent) => {
+ this.setState({
+ [name]: event.target.value,
+ } as Pick);
+ };
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+(OutlinedTextFields as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(OutlinedTextFields);
diff --git a/docs/src/pages/demos/text-fields/TextFieldMargins.js b/docs/src/pages/demos/text-fields/TextFieldMargins.js
index 20e7ca4b7e905a..61571745fbe3e8 100644
--- a/docs/src/pages/demos/text-fields/TextFieldMargins.js
+++ b/docs/src/pages/demos/text-fields/TextFieldMargins.js
@@ -27,6 +27,7 @@ const TextFieldMargins = props => {
className={classes.textField}
helperText="Some important text"
/>
+
{
helperText="Some important text"
margin="dense"
/>
+
+ createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ textField: {
+ marginLeft: theme.spacing.unit,
+ marginRight: theme.spacing.unit,
+ width: 200,
+ },
+ });
+
+export interface Props extends WithStyles {}
+
+const TextFieldMargins = (props: Props) => {
+ const { classes } = props;
+
+ return (
+
+
+
+
+
+ );
+};
+
+TextFieldMargins.propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(TextFieldMargins);
diff --git a/docs/src/pages/demos/text-fields/TextFields.js b/docs/src/pages/demos/text-fields/TextFields.js
index 536e33e8b363cd..e0d1b2f66a294e 100644
--- a/docs/src/pages/demos/text-fields/TextFields.js
+++ b/docs/src/pages/demos/text-fields/TextFields.js
@@ -51,9 +51,7 @@ class TextFields extends React.Component {
};
handleChange = name => event => {
- this.setState({
- [name]: event.target.value,
- });
+ this.setState({ [name]: event.target.value });
};
render() {
@@ -69,6 +67,7 @@ class TextFields extends React.Component {
onChange={this.handleChange('name')}
margin="normal"
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ textField: {
+ marginLeft: theme.spacing.unit,
+ marginRight: theme.spacing.unit,
+ width: 200,
+ },
+ dense: {
+ marginTop: 19,
+ },
+ menu: {
+ width: 200,
+ },
+ });
+
+const currencies = [
+ {
+ value: 'USD',
+ label: '$',
+ },
+ {
+ value: 'EUR',
+ label: '€',
+ },
+ {
+ value: 'BTC',
+ label: '฿',
+ },
+ {
+ value: 'JPY',
+ label: '¥',
+ },
+];
+
+export interface Props extends WithStyles {}
+
+export interface State {
+ name: string;
+ age: string;
+ multiline: string;
+ currency: string;
+}
+
+class TextFields extends React.Component {
+ state: State = {
+ name: 'Cat in the Hat',
+ age: '',
+ multiline: 'Controlled',
+ currency: 'EUR',
+ };
+
+ handleChange = (name: keyof State) => (event: React.ChangeEvent) => {
+ this.setState({ [name]: event.target.value } as Pick);
+ };
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+ );
+ }
+}
+
+(TextFields as React.ComponentClass).propTypes = {
+ classes: PropTypes.object.isRequired,
+} as any;
+
+export default withStyles(styles)(TextFields);
diff --git a/docs/ts-demo-ignore.json b/docs/ts-demo-ignore.json
new file mode 100644
index 00000000000000..fe51488c7066f6
--- /dev/null
+++ b/docs/ts-demo-ignore.json
@@ -0,0 +1 @@
+[]
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
new file mode 100644
index 00000000000000..9c714464e57baa
--- /dev/null
+++ b/docs/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../tsconfig.json",
+ "include": ["types", "src/pages/**/*"]
+}
diff --git a/docs/tslint.json b/docs/tslint.json
new file mode 100644
index 00000000000000..d35877747ecfb7
--- /dev/null
+++ b/docs/tslint.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "no-irregular-whitespace": false,
+ "no-object-literal-type-assertion": false,
+ "no-trailing-whitespace": [true, "ignore-template-strings"]
+ }
+}
diff --git a/docs/types/icons.d.ts b/docs/types/icons.d.ts
new file mode 100644
index 00000000000000..acef22e4e68a6f
--- /dev/null
+++ b/docs/types/icons.d.ts
@@ -0,0 +1,2 @@
+declare module '@material-ui/icons';
+declare module '@material-ui/icons/*';
diff --git a/package.json b/package.json
index 7c9f28dcf2d0c4..047e7929353b1e 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,8 @@
"docs:icons": "rimraf static/icons/* && babel-node ./docs/scripts/buildIcons.js",
"docs:size-why": "DOCS_STATS_ENABLED=true yarn docs:build",
"docs:start": "next start",
+ "docs:typescript": "cross-env NODE_ENV=development babel docs/src --config-file ./docs/babel.config.ts.js --extensions .tsx --out-dir docs/src",
+ "docs:typescript:formatted": "node docs/scripts/formattedTSDemos",
"jsonlint": "yarn --silent jsonlint:files | xargs -n1 jsonlint -q -c && echo \"jsonlint: no lint errors\"",
"jsonlint:files": "find . -name \"*.json\" | grep -v -f .eslintignore",
"lint": "eslint . --cache --report-unused-disable-directives && echo \"eslint: no lint errors\"",
@@ -50,7 +52,7 @@
"test:umd": "yarn babel-node packages/material-ui/test/umd/run.js",
"test:unit": "cross-env NODE_ENV=test mocha 'packages/**/*.test.js' 'docs/**/*.test.js' --exclude '**/node_modules/**'",
"test:watch": "yarn test:unit --watch",
- "typescript": "lerna run typescript"
+ "typescript": "lerna run typescript && tslint -p docs/tsconfig.json"
},
"peerDependencies": {
"react": "*",
@@ -66,27 +68,34 @@
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.0",
"@babel/preset-react": "^7.0.0",
+ "@babel/preset-typescript": "^7.1.0",
"@babel/register": "^7.0.0",
"@date-io/date-fns": "^1.0.0",
"@emotion/core": "^10.0.0",
"@emotion/styled": "^10.0.0",
+ "@types/classnames": "^2.2.6",
"@types/enzyme": "^3.1.4",
"@types/react": "^16.7.10",
+ "@types/react-dom": "^16.0.9",
"@types/react-router-dom": "^4.3.1",
+ "@types/react-text-mask": "^5.4.2",
"@weco/next-plugin-transpile-modules": "0.0.2",
"accept-language": "^3.0.18",
"argos-cli": "^0.0.9",
"autoprefixer": "^9.0.0",
"autosuggest-highlight": "^3.1.1",
+ "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.0",
"babel-loader": "^8.0.0",
"babel-plugin-istanbul": "^5.0.0",
"babel-plugin-module-resolver": "^3.0.0",
"babel-plugin-preval": "^2.0.0",
"babel-plugin-react-remove-properties": "^0.2.5",
+ "babel-plugin-tester": "^5.5.1",
"babel-plugin-transform-dev-warning": "^0.1.0",
"babel-plugin-transform-react-constant-elements": "^6.23.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.21",
+ "babel-plugin-unwrap-createStyles": "link:packages/babel-plugin-unwrap-createStyles",
"chai": "^4.1.2",
"clean-css": "^4.1.11",
"clipboard-copy": "^2.0.0",
diff --git a/packages/babel-plugin-unwrap-createStyles/README.md b/packages/babel-plugin-unwrap-createStyles/README.md
new file mode 100644
index 00000000000000..da7905ce6abbd7
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/README.md
@@ -0,0 +1,41 @@
+## Motivation
+
+We write our demos in TS but want to offer the same demos to JS users.
+Maintaining two versions of the demos is hard and therefore we simply transpile
+the TS demos with babel to JS.
+
+However we have some TS only utility functions that are essentially identity function
+with no side-effects whos only purpose is to defeat type widening. These add noise
+to the JS files and bundle size for everyone.
+
+This plugin unwraps them
+
+## Recognized patterns
+
+```ts
+import { createStyles, withStyles } from '@material-ui/core/styles'
+
+const styles = () => createStyles({});
+
+export default withStyles(styles)(Component);
+```
+
+```js
+import { withStyles } from '@material-ui/core/styles';
+
+const styles = () => ({});
+
+export default withStyles(styles)(Component);
+```
+
+### Missing
+
+- default import from `createStyles`
+- aliased imports
+
+## Why not as a typescript transformer?
+
+Face these issues in chronological order
+1. no config API i.e. transformers are only supported for programmatic transpilation/compilation
+2. since we need to pipe prettier we might as well use a programmatic approach so back to transformer
+3. typescript does not preserve blanklines (Microsoft/TypeScript#843) back to babel plugins
diff --git a/packages/babel-plugin-unwrap-createStyles/package.json b/packages/babel-plugin-unwrap-createStyles/package.json
new file mode 100644
index 00000000000000..703f0855c70c16
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "babel-plugin-unwrap-createStyles",
+ "private": true,
+ "version": "0.0.1",
+ "description": "unwraps createStyles call",
+ "main": "src/index.js",
+ "author": "Material-UI Team",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mui-org/material-ui.git"
+ },
+ "bugs": {
+ "url": "https://github.com/mui-org/material-ui/issues"
+ },
+ "scripts": {
+ "test": "cd ../../ && cross-env NODE_ENV=test mocha packages/babel-plugin-unwrap-createStyles/test"
+ }
+}
diff --git a/packages/babel-plugin-unwrap-createStyles/src/index.js b/packages/babel-plugin-unwrap-createStyles/src/index.js
new file mode 100644
index 00000000000000..1a0a406406d8cc
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/src/index.js
@@ -0,0 +1 @@
+module.exports = require('./unwrapCreateStyles');
diff --git a/packages/babel-plugin-unwrap-createStyles/src/unwrapCreateStyles.js b/packages/babel-plugin-unwrap-createStyles/src/unwrapCreateStyles.js
new file mode 100644
index 00000000000000..912d6b2c045cb3
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/src/unwrapCreateStyles.js
@@ -0,0 +1,51 @@
+/**
+ *
+ * @param {babel.types.ImportDeclaration} param0
+ */
+function isImportFromStyles({ source }) {
+ return source.value === '@material-ui/core/styles';
+}
+
+/**
+ *
+ * @param {babel.types.CallExpression} param0
+ */
+function isCreateStylesCall({ callee }) {
+ return callee.name === 'createStyles';
+}
+
+/**
+ *
+ * @param {babel.types.ImportSpecifier} param0
+ */
+function isCreateStylesImportSepcifier({ imported }) {
+ return imported.name === 'createStyles';
+}
+
+/**
+ * @param {babel.types.CallExpression} expression
+ */
+function unwrapCallExpression(expression) {
+ if (expression.arguments.length !== 1) {
+ throw new Error('need exactly one argument');
+ }
+
+ return expression.arguments[0];
+}
+
+module.exports = function unwrapCreateStyles({ types: t }) {
+ return {
+ visitor: {
+ CallExpression(path) {
+ if (isCreateStylesCall(path.node)) {
+ path.replaceWith(unwrapCallExpression(path.node, t));
+ }
+ },
+ ImportSpecifier(path) {
+ if (isImportFromStyles(path.parent) && isCreateStylesImportSepcifier(path.node)) {
+ path.remove();
+ }
+ },
+ },
+ };
+};
diff --git a/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/static/code.js b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/static/code.js
new file mode 100644
index 00000000000000..a9eed91d6b9d8e
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/static/code.js
@@ -0,0 +1,3 @@
+import { createStyles, withStyles } from '@material-ui/core/styles';
+
+const styles = createStyles({});
diff --git a/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/static/output.js b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/static/output.js
new file mode 100644
index 00000000000000..1e35a7e9582756
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/static/output.js
@@ -0,0 +1,2 @@
+import { withStyles } from '@material-ui/core/styles';
+const styles = {};
diff --git a/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/stylecallback/code.js b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/stylecallback/code.js
new file mode 100644
index 00000000000000..1ba20f5df0dbf8
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/stylecallback/code.js
@@ -0,0 +1,8 @@
+import { createStyles, withStyles } from '@material-ui/core/styles';
+
+const styles = theme => {
+ return createStyles({});
+};
+
+const stylesShorthandStripped = theme => createStyles({});
+const stylesShorthand = theme => createStyles({ a: 1 });
diff --git a/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/stylecallback/output.js b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/stylecallback/output.js
new file mode 100644
index 00000000000000..64c5284d4fe111
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/test/__fixtures__/stylecallback/output.js
@@ -0,0 +1,11 @@
+import { withStyles } from '@material-ui/core/styles';
+
+const styles = theme => {
+ return {};
+};
+
+const stylesShorthandStripped = theme => ({});
+
+const stylesShorthand = theme => ({
+ a: 1
+});
diff --git a/packages/babel-plugin-unwrap-createStyles/test/unwrapCreateStyles.test.js b/packages/babel-plugin-unwrap-createStyles/test/unwrapCreateStyles.test.js
new file mode 100644
index 00000000000000..5bc50dce9d179f
--- /dev/null
+++ b/packages/babel-plugin-unwrap-createStyles/test/unwrapCreateStyles.test.js
@@ -0,0 +1,12 @@
+import pluginTester from 'babel-plugin-tester';
+import * as path from 'path';
+import unwrapCreateStylesPlugin from '../src';
+
+pluginTester({
+ plugin: unwrapCreateStylesPlugin,
+ pluginName: 'unwrapCreateStylesPlugin',
+ fixtures: path.join(__dirname, '__fixtures__'),
+ babelOptions: {
+ root: __dirname,
+ },
+});
diff --git a/packages/material-ui-docs/src/svgIcons/HookLogo.js b/packages/material-ui-docs/src/svgIcons/HookLogo.js
deleted file mode 100644
index 1a422e4d9974b9..00000000000000
--- a/packages/material-ui-docs/src/svgIcons/HookLogo.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* eslint-disable max-len */
-
-import React from 'react';
-import SvgIcon from '@material-ui/core/SvgIcon';
-
-function HookLogo(props) {
- return (
-
-
-
-
-
-
- );
-}
-
-HookLogo.muiName = 'SvgIcon';
-
-export default HookLogo;
diff --git a/packages/material-ui-docs/src/svgIcons/Hooks.js b/packages/material-ui-docs/src/svgIcons/Hooks.js
new file mode 100644
index 00000000000000..9c9e95f70e175b
--- /dev/null
+++ b/packages/material-ui-docs/src/svgIcons/Hooks.js
@@ -0,0 +1,16 @@
+/* eslint-disable max-len */
+
+import React from 'react';
+import SvgIcon from '@material-ui/core/SvgIcon';
+
+function Hooks(props) {
+ return (
+
+
+
+ );
+}
+
+Hooks.muiName = 'SvgIcon';
+
+export default Hooks;
diff --git a/packages/material-ui-docs/src/svgIcons/JSLogo.js b/packages/material-ui-docs/src/svgIcons/JSLogo.js
deleted file mode 100644
index afa99d7e1ee9a9..00000000000000
--- a/packages/material-ui-docs/src/svgIcons/JSLogo.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* eslint-disable max-len */
-
-import React from 'react';
-import SvgIcon from '@material-ui/core/SvgIcon';
-
-function JSLogo(props) {
- return (
-
-
-
-
-
-
- );
-}
-
-JSLogo.muiName = 'SvgIcon';
-
-export default JSLogo;
diff --git a/packages/material-ui-docs/src/svgIcons/Javascript.js b/packages/material-ui-docs/src/svgIcons/Javascript.js
new file mode 100644
index 00000000000000..44902e18ceb820
--- /dev/null
+++ b/packages/material-ui-docs/src/svgIcons/Javascript.js
@@ -0,0 +1,16 @@
+/* eslint-disable max-len */
+
+import React from 'react';
+import SvgIcon from '@material-ui/core/SvgIcon';
+
+function Javascript(props) {
+ return (
+
+
+
+ );
+}
+
+Javascript.muiName = 'SvgIcon';
+
+export default Javascript;
diff --git a/packages/material-ui-docs/src/svgIcons/Typescript.js b/packages/material-ui-docs/src/svgIcons/Typescript.js
new file mode 100644
index 00000000000000..59a40a95470325
--- /dev/null
+++ b/packages/material-ui-docs/src/svgIcons/Typescript.js
@@ -0,0 +1,15 @@
+/* eslint-disable max-len */
+
+import React from 'react';
+import SvgIcon from '@material-ui/core/SvgIcon';
+
+function Typescript(props) {
+ return (
+
+
+
+ );
+}
+
+Typescript.muiName = 'SvgIcon';
+export default Typescript;
diff --git a/packages/material-ui/src/InputLabel/InputLabel.d.ts b/packages/material-ui/src/InputLabel/InputLabel.d.ts
index f66bbe14ec4835..6544668e27e710 100644
--- a/packages/material-ui/src/InputLabel/InputLabel.d.ts
+++ b/packages/material-ui/src/InputLabel/InputLabel.d.ts
@@ -26,6 +26,4 @@ export type InputLabelClassKey =
| 'filled'
| 'outlined';
-declare const InputLabel: React.ComponentType;
-
-export default InputLabel;
+export default class InputLabel extends React.Component {}
diff --git a/pages/demos/app-bar.js b/pages/demos/app-bar.js
index 409b759a7d6979..98ae686094503b 100644
--- a/pages/demos/app-bar.js
+++ b/pages/demos/app-bar.js
@@ -4,7 +4,11 @@ import React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
const req = require.context('docs/src/pages/demos/app-bar', false, /\.md|\.js$/);
-const reqSource = require.context('!raw-loader!../../docs/src/pages/demos/app-bar', false, /\.js$/);
+const reqSource = require.context(
+ '!raw-loader!../../docs/src/pages/demos/app-bar',
+ false,
+ /\.(js|tsx)$/,
+);
const reqPrefix = 'pages/demos/app-bar';
function Page() {
diff --git a/pages/demos/text-fields.js b/pages/demos/text-fields.js
index 1ddc3b075cc46e..e6ec5215556aca 100644
--- a/pages/demos/text-fields.js
+++ b/pages/demos/text-fields.js
@@ -7,7 +7,7 @@ const req = require.context('docs/src/pages/demos/text-fields', false, /\.md|\.j
const reqSource = require.context(
'!raw-loader!../../docs/src/pages/demos/text-fields',
false,
- /\.js$/,
+ /\.(js|tsx)$/,
);
const reqPrefix = 'pages/demos/text-fields';
diff --git a/tsconfig.json b/tsconfig.json
index a1aa50dce3f6de..58e81fa910f3d1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,6 +10,7 @@
"noEmit": true,
"experimentalDecorators": true,
"baseUrl": "./packages",
+ "allowSyntheticDefaultImports": true,
"paths": {
"@material-ui/core": ["./material-ui/src"],
"@material-ui/core/*": ["./material-ui/src/*"],
diff --git a/yarn.lock b/yarn.lock
index 3f6016ded50aa6..accdb1af86fa16 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -404,6 +404,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-syntax-typescript@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.2.0.tgz#55d240536bd314dcbbec70fd949c5cabaed1de29"
+ integrity sha512-WhKr6yu6yGpGcNMVgIBuI9MkredpVc7Y3YR4UzEZmDztHoL6wV56YBHLhWnjO1EvId1B32HrD3DRFc+zSoKI1g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
"@babel/plugin-transform-arrow-functions@^7.0.0", "@babel/plugin-transform-arrow-functions@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"
@@ -668,6 +675,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-transform-typescript@^7.1.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.2.0.tgz#bce7c06300434de6a860ae8acf6a442ef74a99d1"
+ integrity sha512-EnI7i2/gJ7ZNr2MuyvN2Hu+BHJENlxWte5XygPvfj/MbvtOkWor9zcnHpMMQL2YYaaCcqtIvJUyJ7QVfoGs7ew==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-typescript" "^7.2.0"
+
"@babel/plugin-transform-unicode-regex@^7.0.0", "@babel/plugin-transform-unicode-regex@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b"
@@ -790,6 +805,14 @@
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
+"@babel/preset-typescript@^7.1.0":
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.1.0.tgz#49ad6e2084ff0bfb5f1f7fb3b5e76c434d442c7f"
+ integrity sha512-LYveByuF9AOM8WrsNne5+N79k1YxjNB6gmpCQsnuSBAcV8QUeB+ZUxQzL7Rz7HksPbahymKkq2qBR+o36ggFZA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-transform-typescript" "^7.1.0"
+
"@babel/register@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.0.0.tgz#fa634bae1bfa429f60615b754fc1f1d745edd827"
@@ -1660,6 +1683,11 @@
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6"
integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg==
+"@types/classnames@^2.2.6":
+ version "2.2.6"
+ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.6.tgz#dbe8a666156d556ed018e15a4c65f08937c3f628"
+ integrity sha512-XHcYvVdbtAxVstjKxuULYqYaWIzHR15yr1pZj4fnGChuBVJlIAp9StJna0ZJNSgxPh4Nac2FL4JM3M11Tm6fqQ==
+
"@types/enzyme@^3.1.4":
version "3.1.15"
resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.15.tgz#fc9a9695ba9f90cd50c4967e64a8c66ec96913d1"
@@ -1725,6 +1753,13 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18"
integrity sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==
+"@types/react-dom@^16.0.9":
+ version "16.0.11"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.11.tgz#bd10ccb0d9260343f4b9a49d4f7a8330a5c1f081"
+ integrity sha512-x6zUx9/42B5Kl2Vl9HlopV8JF64wLpX3c+Pst9kc1HgzrsH+mkehe/zmHMQTplIrR48H2gpU7ZqurQolYu8XBA==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-router-dom@^4.3.1":
version "4.3.1"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04"
@@ -1742,7 +1777,7 @@
"@types/history" "*"
"@types/react" "*"
-"@types/react-text-mask@^5.4.3":
+"@types/react-text-mask@^5.4.2", "@types/react-text-mask@^5.4.3":
version "5.4.3"
resolved "https://registry.yarnpkg.com/@types/react-text-mask/-/react-text-mask-5.4.3.tgz#51351359306a1feaa4ffed017ba6c7fd4d7a6290"
integrity sha512-+/uibOQ07X4om3+dPMpxUGdw76u4d4HQ9qCPs2syFtwmpTlnOyAJaIW89t+QX/DXI+Ar+SKch6A4stoLg5RdLw==
@@ -2608,7 +2643,7 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
esutils "^2.0.2"
js-tokens "^3.0.2"
-babel-core@7.0.0-bridge.0:
+babel-core@7.0.0-bridge.0, babel-core@^7.0.0-bridge.0:
version "7.0.0-bridge.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
@@ -2980,6 +3015,17 @@ babel-plugin-syntax-trailing-function-commas@^6.22.0:
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=
+babel-plugin-tester@^5.5.1:
+ version "5.5.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-tester/-/babel-plugin-tester-5.5.1.tgz#93648d140e67a81fa8474a73d3382b4fd8e91ca6"
+ integrity sha512-/Sc90Bv2aHGglPkM2Ezw9w/bKMk39h+Dj7G5+Qc9SM2sarnojQhe3NWwZG7cByCXWbp86Gqh4VoYj1YCERFpSg==
+ dependencies:
+ common-tags "^1.4.0"
+ invariant "^2.2.2"
+ lodash.merge "^4.6.0"
+ path-exists "^3.0.0"
+ strip-indent "^2.0.0"
+
babel-plugin-transform-async-generator-functions@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db"
@@ -3288,6 +3334,10 @@ babel-plugin-transform-strict-mode@^6.24.1:
babel-runtime "^6.22.0"
babel-types "^6.24.1"
+"babel-plugin-unwrap-createStyles@link:packages/babel-plugin-unwrap-createStyles":
+ version "0.0.0"
+ uid ""
+
babel-preset-es2015@^6.9.0:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
@@ -4347,6 +4397,11 @@ commander@~2.9.0:
dependencies:
graceful-readlink ">= 1.0.0"
+common-tags@^1.4.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
+ integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==
+
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -9021,6 +9076,11 @@ lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
+lodash.merge@^4.6.0:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
+ integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==
+
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"