From b312efc5e13eb04c40434b7518660aa645369932 Mon Sep 17 00:00:00 2001 From: Olivier Tassinari Date: Mon, 26 Oct 2020 00:01:39 +0100 Subject: [PATCH] [lab] Migrate Timeline to TypeScript --- docs/pages/api-docs/timeline.md | 11 ++-- docs/scripts/buildApi.ts | 6 +-- docs/src/modules/utils/find.js | 2 +- packages/material-ui-lab/package.json | 1 + .../src/Timeline/Timeline.d.ts | 41 --------------- .../{Timeline.test.js => Timeline.test.tsx} | 2 +- .../Timeline/{Timeline.js => Timeline.tsx} | 52 ++++++++++++++++--- .../material-ui-lab/src/Timeline/index.js | 1 - .../src/Timeline/{index.d.ts => index.tsx} | 0 packages/material-ui-lab/tsconfig.build.json | 13 +++++ packages/material-ui-lab/tsconfig.json | 2 +- packages/material-ui/src/index.d.ts | 2 +- .../typescript-to-proptypes/src/generator.ts | 17 +++++- .../typescript-to-proptypes/src/injector.ts | 1 + scripts/generateProptypes.ts | 31 ++++++----- 15 files changed, 107 insertions(+), 75 deletions(-) delete mode 100644 packages/material-ui-lab/src/Timeline/Timeline.d.ts rename packages/material-ui-lab/src/Timeline/{Timeline.test.js => Timeline.test.tsx} (92%) rename packages/material-ui-lab/src/Timeline/{Timeline.js => Timeline.tsx} (51%) delete mode 100644 packages/material-ui-lab/src/Timeline/index.js rename packages/material-ui-lab/src/Timeline/{index.d.ts => index.tsx} (100%) create mode 100644 packages/material-ui-lab/tsconfig.build.json diff --git a/docs/pages/api-docs/timeline.md b/docs/pages/api-docs/timeline.md index a9b65b1a809d7b..5dfc02e404734e 100644 --- a/docs/pages/api-docs/timeline.md +++ b/docs/pages/api-docs/timeline.md @@ -1,5 +1,5 @@ --- -filename: /packages/material-ui-lab/src/Timeline/Timeline.js +filename: /packages/material-ui-lab/src/Timeline/Timeline.tsx --- @@ -11,9 +11,9 @@ filename: /packages/material-ui-lab/src/Timeline/Timeline.js ## Import ```js -import Timeline from '@material-ui/lab/Timeline'; +import Timeline from '@material-ui/lab/Timeline/Timeline.tsx/Timeline'; // or -import { Timeline } from '@material-ui/lab'; +import { Timeline } from '@material-ui/lab/Timeline/Timeline.tsx'; ``` You can learn more about the difference by [reading this guide](/guides/minimizing-bundle-size/). @@ -28,9 +28,6 @@ The `MuiTimeline` name can be used for providing [default props](/customization/ | Name | Type | Default | Description | |:-----|:-----|:--------|:------------| -| align | 'alternate'
| 'left'
| 'right'
| 'left' | The position where the timeline's content should appear. | -| children | node | | The content of the component. | -| classes | object | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. | The `ref` is forwarded to the root element. @@ -51,7 +48,7 @@ You can override the style of the component thanks to one of these customization - With a [global class name](/customization/components/#overriding-styles-with-global-class-names). - With a theme and an [`overrides` property](/customization/globals/#css). -If that's not sufficient, you can check the [implementation of the component](https://github.com/mui-org/material-ui/blob/next/packages/material-ui-lab/src/Timeline/Timeline.js) for more detail. +If that's not sufficient, you can check the [implementation of the component](https://github.com/mui-org/material-ui/blob/next/packages/material-ui-lab/src/Timeline/Timeline.tsx) for more detail. ## Demos diff --git a/docs/scripts/buildApi.ts b/docs/scripts/buildApi.ts index 543fd7d1483765..a91bdfab8f28f1 100644 --- a/docs/scripts/buildApi.ts +++ b/docs/scripts/buildApi.ts @@ -134,8 +134,8 @@ async function annotateComponentDefinition(context: { } const { leadingComments } = node; - const jsdocBlock = leadingComments !== null ? leadingComments[0] : null; - if (leadingComments !== null && leadingComments.length > 1) { + const jsdocBlock = leadingComments != null ? leadingComments[0] : null; + if (leadingComments != null && leadingComments.length > 1) { throw new Error('Should only have a single leading jsdoc block'); } if (jsdocBlock != null) { @@ -372,7 +372,7 @@ async function buildDocs(options: { // no Object.assign to visually check for collisions reactAPI.forwardsRefTo = testInfo.forwardsRefTo; - // if (reactAPI.name !== 'TableCell') { + // if (reactAPI.name !== 'Timeline') { // return; // } diff --git a/docs/src/modules/utils/find.js b/docs/src/modules/utils/find.js index d41c5e368873f3..60ef8d9a6ae72e 100644 --- a/docs/src/modules/utils/find.js +++ b/docs/src/modules/utils/find.js @@ -46,7 +46,7 @@ function findPagesMarkdown( return pagesMarkdown; } -const componentRegex = /^(Unstable_)?([A-Z][a-z]+)+\.js/; +const componentRegex = /^(Unstable_)?([A-Z][a-z]+)+\.(js|tsx)/; /** * Returns the component source in a flat array. diff --git a/packages/material-ui-lab/package.json b/packages/material-ui-lab/package.json index 6252a7d11d41c7..f16178a554c06a 100644 --- a/packages/material-ui-lab/package.json +++ b/packages/material-ui-lab/package.json @@ -29,6 +29,7 @@ "build:node": "node ../../scripts/build node", "build:stable": "node ../../scripts/build stable", "build:copy-files": "node ../../scripts/copy-files.js", + "build:types": "tsc -p tsconfig.build.json", "prebuild": "rimraf build", "release": "yarn build && npm publish build --tag next", "test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/material-ui-lab/**/*.test.{js,ts,tsx}'", diff --git a/packages/material-ui-lab/src/Timeline/Timeline.d.ts b/packages/material-ui-lab/src/Timeline/Timeline.d.ts deleted file mode 100644 index 0b4606c05a79ff..00000000000000 --- a/packages/material-ui-lab/src/Timeline/Timeline.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; -import { InternalStandardProps as StandardProps } from '@material-ui/core'; - -export interface TimelineProps extends StandardProps> { - /** - * The content of the component. - */ - children?: React.ReactNode; - /** - * Override or extend the styles applied to the component. - */ - classes?: { - /** Styles applied to the root element. */ - root?: string; - /** Styles applied to the root element if `align="left"`. */ - alignLeft?: string; - /** Styles applied to the root element if `align="right"`. */ - alignRight?: string; - /** Styles applied to the root element if `align="alternate"`. */ - alignAlternate?: string; - }; - /** - * The position where the timeline's content should appear. - * @default 'left' - */ - align?: 'left' | 'right' | 'alternate'; -} - -export type TimelineClassKey = keyof NonNullable; - -/** - * - * Demos: - * - * - [Timeline](https://material-ui.com/components/timeline/) - * - * API: - * - * - [Timeline API](https://material-ui.com/api/timeline/) - */ -export default function Timeline(props: TimelineProps): JSX.Element; diff --git a/packages/material-ui-lab/src/Timeline/Timeline.test.js b/packages/material-ui-lab/src/Timeline/Timeline.test.tsx similarity index 92% rename from packages/material-ui-lab/src/Timeline/Timeline.test.js rename to packages/material-ui-lab/src/Timeline/Timeline.test.tsx index 1010bc3754d6c4..ffdbc7dd48d813 100644 --- a/packages/material-ui-lab/src/Timeline/Timeline.test.js +++ b/packages/material-ui-lab/src/Timeline/Timeline.test.tsx @@ -4,7 +4,7 @@ import Timeline from './Timeline'; describe('', () => { const mount = createMount(); - let classes; + let classes: Record; before(() => { classes = getClasses(); diff --git a/packages/material-ui-lab/src/Timeline/Timeline.js b/packages/material-ui-lab/src/Timeline/Timeline.tsx similarity index 51% rename from packages/material-ui-lab/src/Timeline/Timeline.js rename to packages/material-ui-lab/src/Timeline/Timeline.tsx index b862e2b5a4e648..5515cc72ad2f7d 100644 --- a/packages/material-ui-lab/src/Timeline/Timeline.js +++ b/packages/material-ui-lab/src/Timeline/Timeline.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; +import { InternalStandardProps as StandardProps } from '@material-ui/core'; import { capitalize } from '@material-ui/core/utils'; -import { withStyles } from '@material-ui/core/styles'; +import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'; import TimelineContext from './TimelineContext'; -export const styles = () => ({ +export const styles = createStyles({ /* Styles applied to the root element. */ root: { display: 'flex', @@ -21,12 +22,41 @@ export const styles = () => ({ alignAlternate: {}, }); -const Timeline = React.forwardRef(function Timeline(props, ref) { +export type TimelineClassKey = keyof WithStyles['classes']; + +export interface TimelineProps extends StandardProps> { + /** + * The content of the component. + */ + children?: React.ReactNode; + /** + * Override or extend the styles applied to the component. + */ + classes: { + /** Styles applied to the root element. */ + root?: string; + /** Styles applied to the root element if `align="left"`. */ + alignLeft?: string; + /** Styles applied to the root element if `align="right"`. */ + alignRight?: string; + /** Styles applied to the root element if `align="alternate"`. */ + alignAlternate?: string; + }; + + /** + * The position where the timeline's content should appear. + * @default 'left' + */ + align?: 'left' | 'right' | 'alternate'; +} + +const Timeline = React.forwardRef(function Timeline(props, ref) { const { align = 'left', classes, className, ...other } = props; return (
    = Omit< > & // each component declares it's classes in a separate interface for proper JSDOC StyledComponentProps & { - ref?: C extends { ref?: infer RefType } ? RefType : React.Ref; + ref?: C extends { ref?: infer RefType } ? RefType : React.Ref; // TODO: Remove implicit props. Up to each component. className?: string; style?: React.CSSProperties; diff --git a/packages/typescript-to-proptypes/src/generator.ts b/packages/typescript-to-proptypes/src/generator.ts index 5eb978145978aa..78e0582d1392c2 100644 --- a/packages/typescript-to-proptypes/src/generator.ts +++ b/packages/typescript-to-proptypes/src/generator.ts @@ -2,6 +2,14 @@ import _ from 'lodash'; import * as t from './types'; export interface GenerateOptions { + /** + * If source itself written in typescript prop-types disable prop-types validation + * by injecting propTypes as + * ```jsx + * .propTypes = { ... } as any + * ``` + */ + disableTypescriptPropTypesValidation?: boolean; /** * Enable/disable the default sorting (ascending) or provide your own sort function * @default true @@ -86,6 +94,7 @@ function defaultSortLiteralUnions(a: t.LiteralType, b: t.LiteralType) { */ export function generate(component: t.Component, options: GenerateOptions = {}): string { const { + disableTypescriptPropTypesValidation = false, sortProptypes = true, importedName = 'PropTypes', includeJSDoc = true, @@ -304,5 +313,11 @@ export function generate(component: t.Component, options: GenerateOptions = {}): options.comment && `// ${options.comment.split(/\r?\n/gm).reduce((prev, curr) => `${prev}\n// ${curr}`)}\n`; - return `${component.name}.propTypes = {\n${comment !== undefined ? comment : ''}${generated}\n}`; + const componentNameNode = disableTypescriptPropTypesValidation + ? `(${component.name} as any)` + : component.name; + + return `${componentNameNode}.propTypes = {\n${ + comment !== undefined ? comment : '' + }${generated}\n}`; } diff --git a/packages/typescript-to-proptypes/src/injector.ts b/packages/typescript-to-proptypes/src/injector.ts index d4a56931b95d3c..239daec0b5f8b1 100644 --- a/packages/typescript-to-proptypes/src/injector.ts +++ b/packages/typescript-to-proptypes/src/injector.ts @@ -381,6 +381,7 @@ export function inject( plugins: [ require.resolve('@babel/plugin-syntax-class-properties'), require.resolve('@babel/plugin-syntax-jsx'), + [require.resolve('@babel/plugin-syntax-typescript'), { isTSX: true }], plugin(propTypes, options, propTypesToInject), ...(babelPlugins || []), ], diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts index 035ed8d2c9b843..1f7d84d9cb9fde 100644 --- a/scripts/generateProptypes.ts +++ b/scripts/generateProptypes.ts @@ -152,9 +152,9 @@ const prettierConfig = prettier.resolveConfig.sync(process.cwd(), { }); async function generateProptypes( - tsFile: string, - jsFile: string, program: ttp.ts.Program, + sourceFile: string, + tsFile: string = sourceFile, ): Promise { const proptypes = ttp.parseFromProgram(tsFile, program, { shouldResolveObject: ({ name }) => { @@ -182,7 +182,9 @@ async function generateProptypes( }); }); - const jsContent = await fse.readFile(jsFile, 'utf8'); + const sourceContent = await fse.readFile(sourceFile, 'utf8'); + + const isTsFile = /(\.(ts|tsx))/.test(sourceFile); const unstyledFile = tsFile.endsWith('Styled.d.ts') ? tsFile.replace(/material-ui-lab|material-ui-core|Styled/g, (matched) => { @@ -191,15 +193,18 @@ async function generateProptypes( }) : null; - const result = ttp.inject(proptypes, jsContent, { + const result = ttp.inject(proptypes, sourceContent, { removeExistingPropTypes: true, + disableTypescriptPropTypesValidation: isTsFile, babelOptions: { - filename: jsFile, + filename: sourceFile, }, comment: [ '----------------------------- Warning --------------------------------', '| These PropTypes are generated from the TypeScript type definitions |', - '| To update them edit the d.ts file and run "yarn proptypes" |', + isTsFile + ? '| To update them edit TypeScript types and run "yarn proptypes" |' + : '| To update them edit the d.ts file and run "yarn proptypes" |', '----------------------------------------------------------------------', ].join('\n'), getSortLiteralUnions, @@ -258,11 +263,11 @@ async function generateProptypes( return GenerateResult.Failed; } - const prettified = prettier.format(result, { ...prettierConfig, filepath: jsFile }); + const prettified = prettier.format(result, { ...prettierConfig, filepath: sourceFile }); const formatted = fixBabelGeneratorIssues(prettified); - const correctedLineEndings = fixLineEndings(jsContent, formatted); + const correctedLineEndings = fixLineEndings(sourceContent, formatted); - await fse.writeFile(jsFile, correctedLineEndings); + await fse.writeFile(sourceFile, correctedLineEndings); return GenerateResult.Success; } @@ -287,7 +292,7 @@ async function run(argv: HandlerArgv) { path.resolve(__dirname, '../packages/material-ui/src'), path.resolve(__dirname, '../packages/material-ui-lab/src'), ].map((folderPath) => - glob('+([A-Z])*/+([A-Z])*.d.ts', { + glob('+([A-Z])*/+([A-Z])*.*@(d.ts|ts|tsx)', { absolute: true, cwd: folderPath, }), @@ -299,13 +304,14 @@ async function run(argv: HandlerArgv) { // Example: Modal/ModalManager.d.ts .filter((filePath) => { const folderName = path.basename(path.dirname(filePath)); - const fileName = path.basename(filePath, '.d.ts'); + const fileName = path.basename(filePath).replace(/(\.d\.ts|\.tsx|\.ts)/g, ''); return fileName === folderName; }) .filter((filePath) => { return filePattern.test(filePath); }); + const program = ttp.createTSProgram(files, tsconfig); const promises = files.map>(async (tsFile) => { @@ -315,7 +321,8 @@ async function run(argv: HandlerArgv) { return GenerateResult.TODO; } - return generateProptypes(tsFile, jsFile, program); + const sourceFile = tsFile.includes('.d.ts') ? tsFile.replace('.d.ts', '.js') : tsFile; + return generateProptypes(program, sourceFile, tsFile); }); const results = await Promise.all(promises);