diff --git a/docs/src/modules/components/withRoot.js b/docs/src/modules/components/withRoot.js index 1b79c956290bab..d62b225fbf0e47 100644 --- a/docs/src/modules/components/withRoot.js +++ b/docs/src/modules/components/withRoot.js @@ -87,6 +87,9 @@ const pages = [ { pathname: '/guides/flow', }, + { + pathname: '/guides/typescript', + }, ], }, { diff --git a/docs/src/pages/getting-started/examples.md b/docs/src/pages/getting-started/examples.md index db4bd22d89369f..e77824f0156a31 100644 --- a/docs/src/pages/getting-started/examples.md +++ b/docs/src/pages/getting-started/examples.md @@ -6,6 +6,7 @@ We host some example projects. You can find them in our [GitHub repository](http - [Create React App](https://github.com/callemall/material-ui/tree/v1-beta/examples/create-react-app) - [Next.js](https://github.com/callemall/material-ui/tree/v1-beta/examples/nextjs) - [Create React App with Flow](https://github.com/callemall/material-ui/tree/v1-beta/examples/create-react-app-with-flow) +- [Create React App with TypeScript](https://github.com/callemall/material-ui/tree/v1-beta/examples/create-react-app-with-typescript) The source code for this documentation site is also included in the repository. This is a slightly more complex project. diff --git a/docs/src/pages/guides/flow.md b/docs/src/pages/guides/flow.md index 5f9da03ca8382d..f7a18d0fb40828 100644 --- a/docs/src/pages/guides/flow.md +++ b/docs/src/pages/guides/flow.md @@ -1,16 +1,4 @@ # Flow -You can add static typing to JavaScript to improve developer productivity and code quality thanks to [Flow](https://github.com/facebook/flow) - -## Installation in your own project - +You can add static typing to JavaScript to improve developer productivity and code quality thanks to [Flow](https://github.com/facebook/flow). Have a look at the [Create React App with Flow](https://github.com/callemall/material-ui/tree/v1-beta/examples/create-react-app-with-flow) example. - -1. Copy `.flowconfig` -1. Change `module.name_mapper` to your project name -1. Copy `flow` dir to seed your own local libdefs (in case you need any) -1. `yarn add -D flow-bin` -1. Decide on `enzyme` - flow is expecting it in your `package.json` because `material-ui` includes reusable `test-utils`. If you do not want it, uncomment `L12` in the `.flowconfig` -1. Copy `package.json` script `flow` -1. `flow-typed install` -1. `yarn flow` diff --git a/docs/src/pages/guides/typescript.md b/docs/src/pages/guides/typescript.md new file mode 100644 index 00000000000000..e68d33a0193330 --- /dev/null +++ b/docs/src/pages/guides/typescript.md @@ -0,0 +1,89 @@ +# TypeScript + +You can add static typing to JavaScript to improve developer productivity and code quality thanks to [TypeScript](https://www.typescriptlang.org/). +Have a look at the [Create React App with TypeScript](https://github.com/callemall/material-ui/tree/v1-beta/examples/create-react-app-with-typescript) example. + +## Usage of `withStyles` + +The usage of `withStyles` in TypeScript can be a little tricky, so it's worth showing some examples. You can first call `withStyles()` to create a decorator function, like so: + +```jsx +const decorate = withStyles(({ palette, spacing }) => ({ + root: { + padding: spacing.unit, + background: palette.background, + color: palette.primary, + }, +})); +``` + +This can then subsequently be used to decorate either a stateless functional component or a class component. Suppose we have in either case the following props: + +```jsx +interface Props { + text: string; + type: TypographyProps['type']; + color: TypographyProps['color']; +} +``` + +Functional components are straightforward: + +```jsx +const DecoratedSFC = decorate(({ text, type, color, classes }) => ( + + {text} + +)); +``` + +Class components are a little more cumbersome. Due to a [current limitation in TypeScript's decorator support](https://github.com/Microsoft/TypeScript/issues/4881), `withStyles` can't be used as a class decorator. Instead, we decorate a class component like so: + +```jsx +const DecoratedClass = decorate( + class extends React.Component> { + render() { + const { text, type, color, classes } = this.props + return ( + + {text} + + ) + } + } +); +``` + +Note that in the class example you didn't need to annotate `` in the call to `decorate`; type inference took care of everything. One caveat is that if your styled component takes _no_ additional props in addition to `classes`. The natural thing would be to write + +```jsx +const DecoratedNoProps = decorate( + class extends React.Component> { + render() { + return ( + + Hello, World! + + ) + } + } +); +``` + +Unfortunately, TypeScript infers the wrong type in this case and you'll have trouble when you go to make an element of this component. In this case, you'll need to provide an explicit `{}` type argument, like so: + +```jsx +const DecoratedNoProps = decorate<{}>( // <-- note the type argument! + class extends React.Component> { + render() { + return ( + + Hello, World! + + ) + } + } +); +``` + +To avoid worrying about this edge case it may be a good habit to always provide an explicit type argument to `decorate`. diff --git a/examples/create-react-app-with-flow/public/index.html b/examples/create-react-app-with-flow/public/index.html index 0de67d7ff7caff..767c9199fadc1c 100644 --- a/examples/create-react-app-with-flow/public/index.html +++ b/examples/create-react-app-with-flow/public/index.html @@ -24,6 +24,9 @@ My page +
+ + + + + + + My page + + + +
+ + + diff --git a/examples/create-react-app-with-typescript/public/manifest.json b/examples/create-react-app-with-typescript/public/manifest.json new file mode 100644 index 00000000000000..be607e41771912 --- /dev/null +++ b/examples/create-react-app-with-typescript/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "192x192", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/create-react-app-with-typescript/src/components/withRoot.tsx b/examples/create-react-app-with-typescript/src/components/withRoot.tsx new file mode 100644 index 00000000000000..626d79b206d946 --- /dev/null +++ b/examples/create-react-app-with-typescript/src/components/withRoot.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import JssProvider from 'react-jss/lib/JssProvider'; +import { withStyles, MuiThemeProvider } from 'material-ui/styles'; +import { wrapDisplayName } from 'recompose'; +import createContext from '../styles/createContext'; + +// Apply some reset +const decorate = withStyles(theme => ({ + '@global': { + html: { + background: theme.palette.background.default, + WebkitFontSmoothing: 'antialiased', // Antialiasing. + MozOsxFontSmoothing: 'grayscale', // Antialiasing. + }, + body: { + margin: 0, + }, + }, +})); + +const AppWrapper = decorate<{ children: JSX.Element }>(props => props.children); + +const context = createContext(); + +function withRoot(BaseComponent: React.ComponentType) { + class WithRoot extends React.Component { + componentDidMount() { + // Remove the server-side injected CSS. + const jssStyles = document.querySelector('#jss-server-side'); + if (jssStyles && jssStyles.parentNode) { + jssStyles.parentNode.removeChild(jssStyles); + } + } + + render() { + return ( + + + + + + + + ); + } + } + + if (process.env.NODE_ENV !== 'production') { + (WithRoot as any).displayName = wrapDisplayName(BaseComponent, 'withRoot'); + } + + return WithRoot; +} + +export default withRoot; diff --git a/examples/create-react-app-with-typescript/src/index.tsx b/examples/create-react-app-with-typescript/src/index.tsx new file mode 100644 index 00000000000000..bc73058296748b --- /dev/null +++ b/examples/create-react-app-with-typescript/src/index.tsx @@ -0,0 +1,5 @@ +import * as React from 'react'; +import { render } from 'react-dom'; +import Index from './pages/index'; + +render(, document.querySelector('#root')); diff --git a/examples/create-react-app-with-typescript/src/pages/index.tsx b/examples/create-react-app-with-typescript/src/pages/index.tsx new file mode 100644 index 00000000000000..699e747258da02 --- /dev/null +++ b/examples/create-react-app-with-typescript/src/pages/index.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import Button from 'material-ui/Button'; +import Dialog, { + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, +} from 'material-ui/Dialog'; +import Typography from 'material-ui/Typography'; +import withStyles, { WithStyles } from 'material-ui/styles/withStyles'; +import withRoot from '../components/withRoot'; + +const styles = { + root: { + textAlign: 'center', + paddingTop: 200, + }, +}; + +type State = { + open: boolean, +}; + +class Index extends React.Component, State> { + state = { + open: false, + }; + + handleRequestClose = () => { + this.setState({ + open: false, + }); + }; + + handleClick = () => { + this.setState({ + open: true, + }); + }; + + render() { + return ( +
+ + Super Secret Password + + 1-2-3-4-5 + + + + + + + Material-UI + + + example project + + +
+ ); + } +} + +export default withRoot(withStyles(styles)<{}>(Index)); diff --git a/examples/create-react-app-with-typescript/src/styles/createContext.ts b/examples/create-react-app-with-typescript/src/styles/createContext.ts new file mode 100644 index 00000000000000..fad404a5622868 --- /dev/null +++ b/examples/create-react-app-with-typescript/src/styles/createContext.ts @@ -0,0 +1,29 @@ +import { create, SheetsRegistry } from 'jss'; +import preset from 'jss-preset-default'; +import { createMuiTheme } from 'material-ui/styles'; +import { purple, green } from 'material-ui/colors'; +import createGenerateClassName from 'material-ui/styles/createGenerateClassName'; + +const theme = createMuiTheme({ + palette: { + primary: purple, + secondary: green, + }, +}); + +// Configure JSS +const jss = create(preset()); +jss.options.createGenerateClassName = createGenerateClassName; + +export const sheetsManager = new Map(); + +export default function createContext() { + return { + jss, + theme, + // This is needed in order to deduplicate the injection of CSS in the page. + sheetsManager, + // This is needed in order to inject the critical CSS. + sheetsRegistry: new SheetsRegistry(), + }; +} diff --git a/examples/create-react-app-with-typescript/tsconfig.json b/examples/create-react-app-with-typescript/tsconfig.json new file mode 100644 index 00000000000000..85973541495b8d --- /dev/null +++ b/examples/create-react-app-with-typescript/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "jsx": "react", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest", + "src/setupTests.ts" + ] +} diff --git a/examples/create-react-app-with-typescript/tsconfig.test.json b/examples/create-react-app-with-typescript/tsconfig.test.json new file mode 100644 index 00000000000000..65ffdd493929cf --- /dev/null +++ b/examples/create-react-app-with-typescript/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs" + } +} \ No newline at end of file diff --git a/examples/create-react-app-with-typescript/tslint.json b/examples/create-react-app-with-typescript/tslint.json new file mode 100644 index 00000000000000..943daa43419f9e --- /dev/null +++ b/examples/create-react-app-with-typescript/tslint.json @@ -0,0 +1,100 @@ +{ + "extends": ["tslint-react"], + "rules": { + "align": [ + true, + "parameters", + "arguments", + "statements" + ], + "ban": false, + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": false, + "forin": true, + "indent": [ true, "spaces" ], + "interface-name": [true, "never-prefix"], + "jsdoc-format": true, + "jsx-boolean-value": false, + "jsx-no-lambda": false, + "jsx-no-multiline-js": false, + "label-position": true, + "max-line-length": [ true, 120 ], + "member-ordering": [ + true, + "public-before-private", + "static-before-instance", + "variables-before-functions" + ], + "no-any": false, + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "log", + "error", + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-consecutive-blank-lines": true, + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-shadowed-variable": true, + "no-string-literal": true, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": false, + "no-unused-expression": true, + "no-use-before-declare": false, + "one-line": [ + true, + "check-catch", + "check-else", + "check-open-brace", + "check-whitespace" + ], + "quotemark": [true, "single", "jsx-double"], + "radix": true, + "semicolon": [true, "always", "ignore-bound-class-methods"], + "switch-default": true, + + "trailing-comma": [false], + + "triple-equals": [ true, "allow-null-check" ], + "typedef": [ + true, + "parameter", + "property-declaration" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"], + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-module", + "check-operator", + "check-separator", + "check-type", + "check-typecast" + ] + } +} diff --git a/examples/create-react-app-with-typescript/typings/misc.d.ts b/examples/create-react-app-with-typescript/typings/misc.d.ts new file mode 100644 index 00000000000000..94ab98b168dc3f --- /dev/null +++ b/examples/create-react-app-with-typescript/typings/misc.d.ts @@ -0,0 +1,3 @@ +declare module 'jss' +declare module 'jss-preset-default' +declare module 'react-jss/*' \ No newline at end of file diff --git a/examples/create-react-app/public/index.html b/examples/create-react-app/public/index.html index 0de67d7ff7caff..767c9199fadc1c 100644 --- a/examples/create-react-app/public/index.html +++ b/examples/create-react-app/public/index.html @@ -24,6 +24,9 @@ My page +