diff --git a/.babelrc.js b/.babelrc.js index c9f1b2e1a2e5..cc5a0880641f 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -24,13 +24,8 @@ module.exports = { "@babel/plugin-syntax-dynamic-import", "pegjs-inline-precompile", "./scripts/babel/proptypes-from-ts-props", + "./scripts/babel/react-docgen-typescript", "add-module-exports", - [ - "react-docgen", - { - "resolver": "findAllExportedComponentDefinitions" - } - ], // stage 3 "@babel/proposal-object-rest-spread", // stage 2 diff --git a/package.json b/package.json index 4b3257a2bf50..4f170b7299c7 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "test-unit": "cross-env NODE_ENV=test jest --config ./scripts/jest/config.json", "test-a11y": "node ./scripts/a11y-testing", "test-staged": "yarn lint && node scripts/test-staged.js", - "start-test-server": "BABEL_MODULES=false NODE_ENV=puppeteer webpack-dev-server --config src-docs/webpack.config.js --port 9999", + "start-test-server": "BABEL_MODULES=false NODE_ENV=puppeteer NODE_OPTIONS=--max-old-space-size=4096 webpack-dev-server --config src-docs/webpack.config.js --port 9999", "test-visual": "wdio test/wdio.conf.js", "yo-component": "yo ./generator-eui/app/component.js", "test-visual-tests": "node ./scripts/run-visual-tests.js", @@ -128,7 +128,6 @@ "babel-plugin-dynamic-import-node": "^2.2.0", "babel-plugin-inline-react-svg": "^1.0.1", "babel-plugin-pegjs-inline-precompile": "^0.1.0", - "babel-plugin-react-docgen": "^3.1.0", "babel-template": "^6.26.0", "cache-loader": "^2.0.1", "chai": "^4.1.2", @@ -191,6 +190,7 @@ "puppeteer": "^2.0.0", "raw-loader": "^0.5.1", "react": "^16.12.0", + "react-docgen-typescript": "^1.17.1", "react-dom": "^16.12.0", "react-redux": "^5.1.2", "react-router": "^5.2.0", diff --git a/scripts/babel/proptypes-from-ts-props/index.js b/scripts/babel/proptypes-from-ts-props/index.js index a897520718a3..246ef94d86cf 100644 --- a/scripts/babel/proptypes-from-ts-props/index.js +++ b/scripts/babel/proptypes-from-ts-props/index.js @@ -1,3 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable new-cap */ const fs = require('fs'); @@ -471,8 +491,7 @@ function getPropTypesForNode(node, optional, state) { if ( types.isLiteral(propType) || - (types.isIdentifier(propType) && - propType.name === 'undefined') + (types.isIdentifier(propType) && propType.name === 'undefined') ) { // can't use a literal straight, wrap it with PropTypes.oneOf([ the_literal ]) propType = convertLiteralToOneOf(types, propType); @@ -511,7 +530,7 @@ function getPropTypesForNode(node, optional, state) { // translates intersections (Foo & Bar & Baz) to a shape with the types' members (Foo, Bar, Baz) merged together case 'TSIntersectionType': const usableNodes = [...node.types].filter(node => { - let nodePropTypes = getPropTypesForNode(node, true, state); + const nodePropTypes = getPropTypesForNode(node, true, state); if ( types.isMemberExpression(nodePropTypes) && @@ -526,7 +545,8 @@ function getPropTypesForNode(node, optional, state) { !types.isCallExpression(nodePropTypes) || !types.isMemberExpression(nodePropTypes.callee) || nodePropTypes.callee.object.name !== 'PropTypes' || - (nodePropTypes.callee.property.name !== 'shape' && nodePropTypes.callee.property.name !== 'oneOfType') + (nodePropTypes.callee.property.name !== 'shape' && + nodePropTypes.callee.property.name !== 'oneOfType') ) { return false; } @@ -712,9 +732,15 @@ function getPropTypesForNode(node, optional, state) { (types.isIdentifier(propertyPropType) && propertyPropType.name === 'undefined') ) { - propertyPropType = convertLiteralToOneOf(types, propertyPropType); + propertyPropType = convertLiteralToOneOf( + types, + propertyPropType + ); if (!property.optional) { - propertyPropType = makePropTypeRequired(types, propertyPropType); + propertyPropType = makePropTypeRequired( + types, + propertyPropType + ); } } @@ -1085,7 +1111,7 @@ const typeDefinitionExtractors = { return []; } - const cacheKey = `${sourceFilename}_${resolvedPath}` + const cacheKey = `${sourceFilename}_${resolvedPath}`; if (importedDefinitionsCache.has(cacheKey)) { return importedDefinitionsCache.get(cacheKey); } @@ -1562,8 +1588,7 @@ module.exports = function propTypesFromTypeScript({ types }) { types.isIdentifier(variableDeclarator.init.callee.object) && variableDeclarator.init.callee.object.name === 'React' && types.isIdentifier(variableDeclarator.init.callee.property) && - variableDeclarator.init.callee.property.name === 'forwardRef') - ) + variableDeclarator.init.callee.property.name === 'forwardRef')) ) { // props for the component come from the second argument to the type params const typeDefinition = diff --git a/scripts/babel/proptypes-from-ts-props/index.test.js b/scripts/babel/proptypes-from-ts-props/index.test.ts similarity index 94% rename from scripts/babel/proptypes-from-ts-props/index.test.js rename to scripts/babel/proptypes-from-ts-props/index.test.ts index 92be17c784d9..eab59be393be 100644 --- a/scripts/babel/proptypes-from-ts-props/index.test.js +++ b/scripts/babel/proptypes-from-ts-props/index.test.ts @@ -1,25 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* eslint-disable @typescript-eslint/no-var-requires */ + const path = require('path'); -const { transform } = require('@babel/core'); +const core = require('@babel/core'); const babelOptions = { babelrc: false, - presets: [ - '@babel/typescript', - ], - plugins: [ - './scripts/babel/proptypes-from-ts-props', - ], + presets: ['@babel/typescript'], + plugins: ['./scripts/babel/proptypes-from-ts-props'], filename: 'somefile.tsx', }; const babelPlugin = require('./index'); +const transform = (input: string, options: object) => { + const result = core.transform(input, options); + result.code = result.code.replace(/[\r\n]+/g, '\n') + return result; +} + beforeEach(() => babelPlugin.clearImportCache()); describe('proptypes-from-ts-props', () => { - describe('proptype generation', () => { - describe('basic generation', () => { - it('imports PropTypes and creates an empty propTypes object on the component', () => { const result = transform( ` @@ -33,11 +52,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = {};`); }); @@ -58,22 +75,18 @@ interface IFooProps {} expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - (function () { if (true) { const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = {}; } })();`); }); - }); describe('primitive propTypes', () => { - it('understands string props', () => { const result = transform( ` @@ -87,11 +100,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.string.isRequired };`); @@ -110,11 +121,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.number.isRequired };`); @@ -133,11 +142,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.bool.isRequired };`); @@ -160,11 +167,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf([undefined]).isRequired, bar: PropTypes.oneOf([null]).isRequired, @@ -187,11 +192,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.func.isRequired };`); @@ -210,11 +213,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.number };`); @@ -239,11 +240,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar1: PropTypes.string.isRequired, bar2: PropTypes.number, @@ -269,11 +268,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf(["bar"]).isRequired, bazz: PropTypes.oneOf([5]) @@ -295,17 +292,14 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = {};`); }); }); describe('function propTypes', () => { - it('understands function props on interfaces', () => { const result = transform( ` @@ -324,11 +318,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.func.isRequired, bar: PropTypes.func, @@ -355,11 +347,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.func.isRequired, bar: PropTypes.func, @@ -367,13 +357,10 @@ FooComponent.propTypes = { buzz: PropTypes.func };`); }); - }); describe('enum / oneOf propTypes', () => { - describe('union type', () => { - it('understands a union of strings', () => { const result = transform( ` @@ -387,11 +374,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { flower: PropTypes.oneOf(["daisy", "daffodil", "dandelion"]).isRequired };`); @@ -410,11 +395,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { prime: PropTypes.oneOf([2, 3, 5, 7, 11, 13]).isRequired };`); @@ -433,11 +416,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { visible: PropTypes.oneOf([true, false]).isRequired };`); @@ -456,11 +437,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bool: PropTypes.oneOf([true, false, "FileNotFound"]).isRequired };`); @@ -479,20 +458,16 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.oneOf(["hello", "world"]) };`); }); - }); describe('enum', () => { - it('understands enum of strings', () => { const result = transform( ` @@ -511,18 +486,14 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; var Foo; - (function (Foo) { Foo["bar"] = "BAR"; Foo["baz"] = "BAZ"; })(Foo || (Foo = {})); - ; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf(["BAR", "BAZ"]).isRequired };`); @@ -546,18 +517,14 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; var Foo; - (function (Foo) { Foo[Foo["bar"] = 3] = "bar"; Foo[Foo["baz"] = 54] = "baz"; })(Foo || (Foo = {})); - ; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf([3, 54]).isRequired };`); @@ -582,19 +549,15 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; var Foo; - (function (Foo) { Foo["bar"] = "BAR"; Foo[Foo["baz"] = 5] = "baz"; Foo[Foo["buzz"] = false] = "buzz"; })(Foo || (Foo = {})); - ; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf(["BAR", 5, false]).isRequired };`); @@ -618,27 +581,21 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; var Foo; - (function (Foo) { Foo["bar"] = "BAR"; Foo["baz"] = "BAZ"; })(Foo || (Foo = {})); - ; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf(["BAR", "BAZ"]) };`); }); - }); describe('keyof typeof', () => { - it('understands keyof typeof', () => { const result = transform( ` @@ -660,22 +617,17 @@ const FooMap = { foo: 'bar', fizz: 'buzz' }; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf(["foo", "fizz"]).isRequired };`); }); - }); - }); describe('object / shape propTypes', () => { - it('understands an object of primitive values', () => { const result = transform( ` @@ -690,11 +642,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { person: PropTypes.shape({ name: PropTypes.string.isRequired, @@ -719,11 +669,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { fizz: PropTypes.shape({ bar: PropTypes.shape({ @@ -736,13 +684,10 @@ FooComponent.propTypes = { }).isRequired };`); }); - }); describe('React component & element propTypes', () => { - describe('element propType', () => { - it('understands React.Component', () => { const result = transform( ` @@ -756,11 +701,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -779,11 +722,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -802,11 +743,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -825,11 +764,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -848,11 +785,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -871,11 +806,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -894,11 +827,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -917,11 +848,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.element.isRequired };`); @@ -939,20 +868,16 @@ const FooComponent: React.SFC<{foo: JSXElementConstructor}> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.func.isRequired };`); }); - }); describe('node propType', () => { - it('understands React.ReactNode', () => { const result = transform( ` @@ -966,11 +891,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.node.isRequired };`); @@ -989,20 +912,16 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.node.isRequired };`); }); - }); describe('elementType propType', () => { - it('understands React.ComponentType', () => { const result = transform( ` @@ -1016,11 +935,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.elementType.isRequired, bar: PropTypes.elementType.isRequired @@ -1040,23 +957,18 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.elementType.isRequired, bar: PropTypes.elementType.isRequired };`); }); - }); - }); describe('intersection types', () => { - it('intersects multiple types together', () => { const result = transform( ` @@ -1072,11 +984,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { fizz: PropTypes.shape({ name: PropTypes.string.isRequired, @@ -1100,11 +1010,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { fizz: PropTypes.shape({ name: PropTypes.string.isRequired, @@ -1128,22 +1036,18 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { buzz: PropTypes.bool.isRequired, foo: PropTypes.string.isRequired, bar: PropTypes.number };`); }); - }); describe('union types', () => { - it('unions primitive types and values', () => { const result = transform( ` @@ -1157,11 +1061,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.oneOf([5, 6])]).isRequired };`); @@ -1182,11 +1084,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { buzz: PropTypes.oneOfType([PropTypes.shape({ foo: PropTypes.string.isRequired, @@ -1214,11 +1114,9 @@ const FooComponent: React.SFC> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { d: PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]).isRequired, foo: PropTypes.oneOfType([PropTypes.string, PropTypes.string.isRequired]), @@ -1244,11 +1142,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { href: PropTypes.string, onClick: PropTypes.func, @@ -1273,11 +1169,9 @@ const FooComponent: React.SFC> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { type: PropTypes.oneOfType([PropTypes.oneOf(["foo"]).isRequired, PropTypes.oneOf(["bar"]).isRequired]).isRequired, value: PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]).isRequired @@ -1297,20 +1191,16 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.oneOf(["five", null, undefined]).isRequired };`); }); - }); describe('array / arrayOf propTypes', () => { - describe('Array', () => { it('understands an Array of strings', () => { const result = transform( @@ -1325,11 +1215,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired };`); @@ -1348,11 +1236,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired };`); @@ -1371,11 +1257,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.bool.isRequired).isRequired };`); @@ -1394,11 +1278,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.func.isRequired).isRequired };`); @@ -1417,11 +1299,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.oneOf(["foo", "bar"]).isRequired).isRequired };`); @@ -1440,11 +1320,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.oneOf([5, 6])]).isRequired).isRequired };`); @@ -1463,11 +1341,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]).isRequired) };`); @@ -1487,11 +1363,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.shape({ foo: PropTypes.string.isRequired, @@ -1515,11 +1389,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired };`); @@ -1538,11 +1410,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired };`); @@ -1561,11 +1431,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.bool.isRequired).isRequired };`); @@ -1584,11 +1452,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.func.isRequired).isRequired };`); @@ -1608,11 +1474,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.oneOf(["foo", "bar"]).isRequired).isRequired };`); @@ -1631,11 +1495,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.oneOf([5, 6])]).isRequired).isRequired };`); @@ -1657,13 +1519,11 @@ const Foo: React.SFC = ({ option }: Props) => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const Foo = ({ option }) => { return
{option.type == 'a' ? option.value : option.valid}
; }; - Foo.propTypes = { option: PropTypes.oneOfType([PropTypes.shape({ type: PropTypes.oneOf(["a"]).isRequired, @@ -1688,11 +1548,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string.isRequired, PropTypes.number.isRequired]).isRequired) };`); @@ -1712,11 +1570,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.arrayOf(PropTypes.shape({ foo: PropTypes.string.isRequired, @@ -1725,11 +1581,9 @@ FooComponent.propTypes = { };`); }); }); - }); describe('type and interface resolving', () => { - it('understands inline definitions', () => { const result = transform( ` @@ -1742,11 +1596,9 @@ const FooComponent: React.SFC<{bar: string}> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.string.isRequired };`); @@ -1765,11 +1617,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.string.isRequired };`); @@ -1789,21 +1639,17 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.string.isRequired };`); }); describe('external references', () => { - describe('non-resolvable', () => { - - it(`doesn't set propTypes if the whole type is un-resolvable`, () => { + it("doesn't set propTypes if the whole type is un-resolvable", () => { const result = transform( ` import React from 'react'; @@ -1814,7 +1660,6 @@ const FooComponent: React.SFC = () => { ); expect(result.code).toBe(`import React from 'react'; - const FooComponent = () => { return
Hello World
; };`); @@ -1832,11 +1677,9 @@ const FooComponent: React.SFC<{foo: Foo, bar?: Bar}> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.any.isRequired, bar: PropTypes.any @@ -1854,7 +1697,6 @@ const FooComponent: React.SFC> = () => { ); expect(result.code).toBe(`import React from 'react'; - const FooComponent = () => { return
Hello World
; };`); @@ -1873,11 +1715,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { fizz: PropTypes.any.isRequired };`); @@ -1897,11 +1737,9 @@ const FooComponent: React.SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { fizz: PropTypes.shape({ name: PropTypes.string.isRequired, @@ -1909,11 +1747,9 @@ FooComponent.propTypes = { }).isRequired };`); }); - }); describe('local references', () => { - it('resolves types from relative imports', () => { const result = transform( ` @@ -1931,27 +1767,26 @@ const FooComponent: React.SFC<{foo: Foo, bar?: Bar} & CommonProps> = () => { fs: { existsSync: () => true, statSync: () => ({ isDirectory: () => false }), - readFileSync: () => Buffer.from(` + readFileSync: () => + Buffer.from(` export interface CommonProps { className?: string; 'aria-label'?: string; 'data-test-subj'?: string; } - `) - } - } + `), + }, + }, ], - ] + ], } ); expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.any.isRequired, bar: PropTypes.any, @@ -1980,8 +1815,16 @@ const FooComponent: React.SFC<{foo: Foo, bar?: Bar} & CommonProps> = () => { existsSync: () => true, statSync: () => ({ isDirectory: () => true }), readFileSync: filepath => { - if (filepath !== path.resolve(process.cwd(), `common${path.sep}index.ts`)) { - throw new Error('Test case should only try to read file unknown/common/index.ts'); + if ( + filepath !== + path.resolve( + process.cwd(), + `common${path.sep}index.ts` + ) + ) { + throw new Error( + 'Test case should only try to read file unknown/common/index.ts' + ); } return Buffer.from(` @@ -1991,21 +1834,19 @@ const FooComponent: React.SFC<{foo: Foo, bar?: Bar} & CommonProps> = () => { 'data-test-subj'?: string; } `); - } - } - } + }, + }, + }, ], - ] + ], } ); expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.any.isRequired, bar: PropTypes.any, @@ -2032,7 +1873,8 @@ const FooComponent: React.SFC = () => { fs: { existsSync: () => true, statSync: () => ({ isDirectory: () => false }), - readFileSync: () => Buffer.from(` + readFileSync: () => + Buffer.from(` interface FooProps { foo: string } @@ -2041,21 +1883,19 @@ const FooComponent: React.SFC = () => { 'aria-label'?: string; 'data-test-subj'?: string; } - `) - } - } + `), + }, + }, ], - ] + ], } ); expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { className: PropTypes.string, "aria-label": PropTypes.string, @@ -2081,7 +1921,10 @@ const FooComponent: React.SFC = () => { existsSync: () => true, statSync: () => ({ isDirectory: () => false }), readFileSync: filepath => { - if (filepath === path.resolve(process.cwd(), 'common.ts')) { + if ( + filepath === + path.resolve(process.cwd(), 'common.ts') + ) { return Buffer.from(` import { FooType } from './types.ts'; export interface CommonProps { @@ -2091,26 +1934,26 @@ const FooComponent: React.SFC = () => { foo: FooType; } `); - } else if (filepath === path.resolve(process.cwd(), 'types.ts')) { + } else if ( + filepath === path.resolve(process.cwd(), 'types.ts') + ) { return Buffer.from(` export type FooType = "Foo" | "Bar" | "Fizz"; `); } - } - } - } + }, + }, + }, ], - ] + ], } ); expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { className: PropTypes.string, "aria-label": PropTypes.string, @@ -2137,31 +1980,35 @@ const FooComponent: React.SFC = () => { existsSync: () => true, statSync: () => ({ isDirectory: () => false }), readFileSync: filepath => { - if (filepath === path.resolve(process.cwd(), 'types', 'foo.ts')) { + if ( + filepath === + path.resolve(process.cwd(), 'types', 'foo.ts') + ) { return Buffer.from(` import { IFoo } from '../interfaces/foo.ts'; export type Foo = IFoo; `); - } else if (filepath === path.resolve(process.cwd(), 'interfaces', 'foo.ts')) { + } else if ( + filepath === + path.resolve(process.cwd(), 'interfaces', 'foo.ts') + ) { return Buffer.from(` export interface IFoo { bar: string } `); } - } - } - } + }, + }, + }, ], - ] + ], } ); expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.string.isRequired };`); @@ -2184,28 +2031,26 @@ const FooComponent: React.SFC<{foo: keyof typeof commonKeys, bar?: commonKeyType fs: { existsSync: () => true, statSync: () => ({ isDirectory: () => false }), - readFileSync: () => Buffer.from(` + readFileSync: () => + Buffer.from(` export const commonKeys = { s: 'small', 'l': 'large', }; - export type commonKeyTypes = keyof typeof commonKeys; - `) - } - } + `), + }, + }, ], - ] + ], } ); expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.oneOf(["s", "l"]).isRequired, bar: PropTypes.oneOf(["s", "l"]) @@ -2242,31 +2087,28 @@ const FooComponent: React.SFC<{foo: Foo}> = () => { `); } - throw new Error(`Test tried to import from ${filepath}`); - } - } - } + throw new Error( + `Test tried to import from ${filepath}` + ); + }, + }, + }, ], - ] + ], } ); expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.string.isRequired };`); }); - }); - }); - }); describe('indexed property access', () => { @@ -2285,11 +2127,9 @@ const FooComponent: React.SFC<{bar: Foo['foo']}> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { bar: PropTypes.func.isRequired };`); @@ -2297,7 +2137,6 @@ FooComponent.propTypes = { }); describe('supported component declarations', () => { - it('annotates React.SFC components', () => { const result = transform( ` @@ -2310,11 +2149,9 @@ const FooComponent: React.SFC<{foo: string, bar?: number}> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2333,11 +2170,9 @@ const FooComponent: SFC<{foo: string, bar?: number}> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2356,11 +2191,9 @@ const FooComponent: FunctionComponent<{foo: string, bar?: number}> = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2380,11 +2213,9 @@ const FooComponent: FooType = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2403,11 +2234,9 @@ const FooComponent: React.FunctionComponent<{foo: string, bar?: number}> = () => expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2428,14 +2257,11 @@ class FooComponent extends React.Component<{foo: string, bar?: number}> { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - class FooComponent extends React.Component { render() { return
Hello World
; } - } - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2456,14 +2282,11 @@ class FooComponent extends React.PureComponent<{foo: string, bar?: number}> { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - class FooComponent extends React.PureComponent { render() { return
Hello World
; } - } - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2484,14 +2307,11 @@ class FooComponent extends Component<{foo: string, bar?: number}> { expect(result.code).toBe(`import React, { Component } from 'react'; import PropTypes from "prop-types"; - class FooComponent extends Component { render() { return
Hello World
; } - } - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number @@ -2512,25 +2332,20 @@ class FooComponent extends PureComponent<{foo: string, bar?: number}> { expect(result.code).toBe(`import React, { PureComponent } from 'react'; import PropTypes from "prop-types"; - class FooComponent extends PureComponent { render() { return
Hello World
; } - } - FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number };`); }); - }); describe('comments', () => { - - it('copies comments from types to proptypes', () => { + it('copies comments from types to proptypes', () => { const result = transform( ` import React, { SFC } from 'react'; @@ -2550,15 +2365,12 @@ const FooComponent: SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { // this is the foo prop foo: PropTypes.string.isRequired, - /** * this is the optional bar prop */ @@ -2566,7 +2378,7 @@ FooComponent.propTypes = { };`); }); - it('copies comments from intersected types', () => { + it('copies comments from intersected types', () => { const result = transform( ` import React, { SFC } from 'react'; @@ -2590,28 +2402,23 @@ const FooComponent: SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { /* bar's foo */ // this is the foo prop foo: PropTypes.string.isRequired, - /** * this is the optional bar prop */ bar: PropTypes.number };`); }); - }); describe('self-referencing types', () => { - - it(`doesn't explode on self-referencing interfaces`, () => { + it("doesn't explode on self-referencing interfaces", () => { const result = transform( ` import React, { SFC } from 'react'; @@ -2627,18 +2434,16 @@ const FooComponent: SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { label: PropTypes.string.isRequired, children: PropTypes.arrayOf(PropTypes.any.isRequired) };`); }); - it(`doesn't explode on self-referencing types`, () => { + it("doesn't explode on self-referencing types", () => { const result = transform( ` import React, { SFC } from 'react'; @@ -2654,17 +2459,14 @@ const FooComponent: SFC = () => { expect(result.code).toBe(`import React from 'react'; import PropTypes from "prop-types"; - const FooComponent = () => { return
Hello World
; }; - FooComponent.propTypes = { label: PropTypes.string.isRequired, children: PropTypes.arrayOf(PropTypes.any.isRequired) };`); }); - }); describe('forwardRef', () => { @@ -2800,15 +2602,15 @@ export { Foo, A } from './foo'; } throw new Error(`Test tried to import from ${filepath}`); - } - } - } + }, + }, + }, ], - ] + ], } ); - expect(result.code).toBe(`export { A } from './foo';`); + expect(result.code).toBe("export { A } from './foo';"); }); it('removes type exports from ExportNamedDeclaration when the imported name differs from the exported one', () => { @@ -2834,15 +2636,15 @@ export { Foo as Bar, A as B } from './foo'; } throw new Error(`Test tried to import from ${filepath}`); - } - } - } + }, + }, + }, ], - ] + ], } ); - expect(result.code).toBe(`export { A as B } from './foo';`); + expect(result.code).toBe("export { A as B } from './foo';"); }); it('removes type export statements', () => { @@ -2865,9 +2667,8 @@ export { DraggableLocation, Draggable, DragDropContextProps } from 'react-beauti ); expect(result.code).toBe( - `export { Draggable } from 'react-beautiful-dnd';` + "export { Draggable } from 'react-beautiful-dnd';" ); }); }); - }); diff --git a/scripts/babel/react-docgen-typescript.js b/scripts/babel/react-docgen-typescript.js new file mode 100644 index 000000000000..a04fdebce4e2 --- /dev/null +++ b/scripts/babel/react-docgen-typescript.js @@ -0,0 +1,416 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* eslint-disable @typescript-eslint/no-var-requires */ + +const propsParser = require('react-docgen-typescript'); +const template = require('@babel/template'); +const ts = require('typescript'); +const glob = require('glob'); +const util = require('util'); +const { SyntaxKind } = require('typescript'); + +const files = [ + ...glob.sync('src/**/*.{ts,tsx}', { absolute: true }), + ...glob.sync('src-docs/**/*.{ts,tsx}', { absolute: true }), +]; + +/** + * To support extended props from tsx files. + */ +const options = { + jsx: ts.JsxEmit.React, +}; + +const program = ts.createProgram(files, options); + +module.exports = function({ types }) { + return { + pre() { + this.fileProcessed = false; + }, + visitor: { + Program: { + enter: function visitNode(path, state) { + const { filename } = state.file.opts; + + if (this.fileProcessed) return; + + this.fileProcessed = true; + + // find if components extends types from other modules + const componentExtends = []; + + // props that should be whitelisted even if its from an external module + const whiteListedProps = ['children', 'className', 'aria-label']; + + // external modules whose props must be whitelisted + const whiteListedParent = [ + 'DragDropContextProps', + 'DraggableProps', + 'DroppableProps', + ]; + + let docgenResults = []; + try { + docgenResults = propsParser + .withDefaultConfig({ + propFilter: (prop, component) => + filterProp( + prop, + component, + state, + whiteListedProps, + whiteListedParent, + componentExtends + ), + shouldExtractLiteralValuesFromEnum: true, + shouldRemoveUndefinedFromOptional: true, + savePropValueAsString: true, + }) + .parseWithProgramProvider(filename, () => program); + // eslint-disable-next-line no-empty + } catch (e) {} + + /** + * react-docgen-typescript takes type of children from PropsWithChildren of FunctionComponent, + * For our case we need our custom types to replace them. + */ + if (state.get('childrenProp') && state.get('componentName')) { + getChildrenTypeFromPropTypes( + state.get('childrenProp'), + state.get('componentName'), + program, + filename + ); + } + + if (docgenResults.length !== 0) { + docgenResults.forEach(function(docgenResult) { + const exportName = docgenResult.displayName; + docgenResult.extends = componentExtends; + path.node.body.push( + template.default.ast(` + try{ + ${exportName}.__docgenInfo = ${util.inspect(docgenResult, { + showHidden: false, + depth: null, + maxArrayLength: null, + })} + } catch(e) {} + `) + ); + }); + } + + // get all the exported types and interfaces of all the files to the state remove their exported + // declarations in the exit stage + if (!state.get('exportedTypes')) { + let allExportedTypes = []; + program.getSourceFiles().forEach(source => { + const exportedTypes = source + .getChildAt(0) + .getChildren() + .filter(child => { + if ( + child.kind !== SyntaxKind.InterfaceDeclaration && + child.kind !== SyntaxKind.TypeAliasDeclaration + ) + return false; + // verify this interface is exported + const isExported = + child.modifiers && + child.modifiers.reduce((isExported, modifier) => { + if (isExported) return isExported; + if (modifier.kind === SyntaxKind.ExportKeyword) + return true; + return false; + }, false); + return isExported; + }) + .map(type => type.name.escapedText); + allExportedTypes = [...allExportedTypes, ...exportedTypes]; + }); + state.set('exportedTypes', allExportedTypes); + } + }, + exit: function exitProgram(path, state) { + // remove any exported identifiers that are TS types or interfaces + // this prevents TS-only identifiers from leaking into ES code + path.traverse({ + ExportNamedDeclaration: nodePath => { + const specifiers = nodePath.get('specifiers'); + const typeDefinitions = state.get('exportedTypes'); + const source = nodePath.get('source'); + specifiers.forEach(specifierPath => { + if (types.isExportSpecifier(specifierPath)) { + const { + node: { local }, + } = specifierPath; + if (types.isIdentifier(local)) { + const { name } = local; + if (typeDefinitions.includes(name)) { + // this is a locally-known value + specifierPath.remove(); + } else if (types.isStringLiteral(source)) { + const libraryName = source.get('value').node; + const isRelativeSource = libraryName.startsWith('.'); + if (isRelativeSource === false) { + // comes from a 3rd-party library + // best way to reliably check if this is + // a type or value is to require the + // library and check its exports + const library = require(libraryName); + if (library.hasOwnProperty(name) === false) { + specifierPath.remove(); + } + } + } + } + } + }); + }, + }); + }, + }, + }, + }; +}; + +/** + * Filter props to remove props from node modules while keeping those whitelisted + * + * @param {*} prop + * @param {*} state + * @param {*} whiteListedProps + * @param {*} whiteListedParent + * @param {*} componentExtends + */ +function filterProp( + prop, + component, + state, + whiteListedProps, + whiteListedParent, + componentExtends +) { + if (prop.name === 'children') { + state.set('childrenProp', prop); + state.set('componentName', component.name); + } + if (whiteListedProps.includes(prop.name)) { + return true; + } + + // if prop type is string | number typescript takes it as ReactText if HTMLAttributes are extended + // in the interace in that case replace it with "string | number" + if (prop.type.name === 'ReactText') { + prop.type.name = 'string | number'; + } + + // if prop.type is ReactElement it will be expanded to show all the supported + // react element types that makes the list too long in this case we could show + // it as ReactElement + prop.type.name = prop.type.name.replace( + reactElementTypeExpanded, + 'ReactElement' + ); + prop.type.name = prop.type.name.replace(reactNodeTypeExpanded, 'ReactNode'); + + // prop.type is key of HTMLElement then all the html attributes will be shown + // in that case we could only show it as any HTML Elements + if (prop.type.name === 'enum') { + const propValueArray = prop.type.value.map(type => type.value); + const found = intrinsicValuesRaw.every( + value => propValueArray.indexOf(value) >= 0 + ); + if (found) { + prop.type.name = 'any HTML Element'; + } + } + + if (prop.parent) { + //Check if props are extended from other node module + if (whiteListedParent.includes(prop.parent.name)) return true; + if ( + prop.parent.name === 'DOMAttributes' && + !componentExtends.includes('DOMAttributes') + ) { + componentExtends.push('DOMAttributes'); + } + if (prop.name.includes(whiteListedProps)) { + return true; + } + return !prop.parent.fileName.includes('node_modules'); + } + return true; +} + +/** + * Parser takes type generated for children prop from PropsWithChildren. Here + * children prop is parsed from the interface used the component by reusing + * typescript program. + * + * @param {*} initialProp + * @param {*} componentName + * @param {*} program + * @param {*} filename + */ + +function getChildrenTypeFromPropTypes( + initialProp, + componentName, + program, + filename +) { + const source = program.getSourceFile(filename); + const checker = program.getTypeChecker(); + + // Get the symbol property of the source file + const moduleSymbol = checker.getSymbolAtLocation(source); + + // Components must be mostly exported. + const components = checker.getExportsOfModule(moduleSymbol); + + /** + * A single file may contain many components. Filter the component whose children prop + * has to be updated + */ + const componentToParse = components.filter( + component => component.escapedName === componentName + )[0]; + + /** + * If there are no declarations, then there will be no interfaces. + */ + if ( + !!componentToParse.declarations && + componentToParse.declarations.length === 0 + ) { + return null; + } + + const declaration = + componentToParse.valueDeclaration || + (componentToParse.declarations && componentToParse.declarations[0]); + + // get Type of the component symbol + const type = checker.getTypeOfSymbolAtLocation(componentToParse, declaration); + + // For stateless components there will be callSignatures. + const callSignatures = type.getCallSignatures(); + if (callSignatures && callSignatures.length) { + for (const sig of callSignatures) { + const params = sig.getParameters(); + // if there are no parameters then there will be no props + if (params.length === 0) { + continue; + } + const propsParam = params[0]; + if (propsParam.name === 'props') { + replaceProp(propsParam, checker, initialProp); + } + } + } else { + // For for statefull components there will be constructSignatures. + const constructSignatures = type.getConstructSignatures(); + if (constructSignatures && constructSignatures.length) { + for (const sig of constructSignatures) { + const instanceType = sig.getReturnType(); + const props = instanceType.getProperty('props'); + if (props.valueDeclaration) { + replaceProp(props, checker, initialProp); + } + } + } + } +} + +/** + * Replace children prop type and required from information from interface + * + * @param {*} props + * @param {*} checker + * @param {*} initialProp + */ +function replaceProp(props, checker, initialProp) { + const propsType = checker.getTypeOfSymbolAtLocation( + props, + props.valueDeclaration + ); + // get all the props of the interface + const propTypes = propsType.getProperties(); + // filter to get the children prop + const childrenProp = propTypes.filter( + prop => prop.getName() === 'children' + )[0]; + /** + * get the first declaration of the props, skip if children prop is from DOMAttributes, + * propsWithChildren declaration only occurs last + */ + const prop = childrenProp.declarations.filter( + declarations => declarations.parent.symbol.name !== 'DOMAttributes' + )[0]; + if (prop) { + prop.symbol.parent.members.forEach((value, key) => { + if (key === 'children') { + const propType = checker.getTypeOfSymbolAtLocation( + value, + value.valueDeclaration + ); + const type = checker.typeToString(propType); + initialProp.required = !prop.questionToken; + initialProp.type.name = type.replace(' | undefined', ''); + initialProp.type.name = initialProp.type.name.replace( + reactElementTypeExpanded, + 'ReactElement' + ); + initialProp.type.name = initialProp.type.name.replace( + reactNodeTypeExpanded, + 'ReactNode' + ); + } + }); + } +} + +/** + * For types declared as (key of HTMLElements) all the HTML Element types will appear. This creates a large + * list of types. To avoid this we could check if the props the props include the firse few keys of HTMLElements + */ +const intrinsicValuesRaw = [ + '"a"', + '"abbr"', + '"address"', + '"animate"', + '"animateMotion"', + '"animateTransform"', + '"area"', + '"article"', + '"aside"', + '"audio"', +]; + +/** + * Replace ReactElement and ReactNode expanded types with ReactElement and ReactNode + */ +const reactElementTypeExpanded = + 'ReactElement ReactElement Component)>) | (new (props: any) => Component)>'; + +const reactNodeTypeExpanded = + 'string | number | boolean | {} | ReactElement ReactElement Component)>) | (new (props: any) => Component<...>)> | ... 5 more ... | (ReactPortal & string)'; diff --git a/scripts/compile-eui.js b/scripts/compile-eui.js index 0fbee3816742..c56bdde46db2 100755 --- a/scripts/compile-eui.js +++ b/scripts/compile-eui.js @@ -116,7 +116,7 @@ function compileBundle() { }); console.log('Building minified bundle...'); - execSync('NODE_ENV=production webpack --config=src/webpack.config.js', { + execSync('NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 webpack --config=src/webpack.config.js', { stdio: 'inherit', env: { ...process.env, diff --git a/src-docs/src/components/guide_section/guide_section.js b/src-docs/src/components/guide_section/guide_section.js index 61ca6977257c..dfe86d289e18 100644 --- a/src-docs/src/components/guide_section/guide_section.js +++ b/src-docs/src/components/guide_section/guide_section.js @@ -27,26 +27,34 @@ import { cleanEuiImports } from '../../services'; export const markup = text => { const regex = /(#[a-zA-Z]+)|(`[^`]+`)/g; - return text.split(regex).map((token, index) => { - if (!token) { - return ''; - } - if (token.startsWith('#')) { - const id = token.substring(1); - const onClick = () => { - document.getElementById(id).scrollIntoView(); - }; - return ( - - {id} - - ); - } - if (token.startsWith('`')) { - const code = token.substring(1, token.length - 1); - return {code}; - } - return token; + return text.split('\n').map(token => { + const values = token.split(regex).map((token, index) => { + if (!token) { + return ''; + } + if (token.startsWith('#')) { + const id = token.substring(1); + const onClick = () => { + document.getElementById(id).scrollIntoView(); + }; + return ( + + {id} + + ); + } + if (token.startsWith('`')) { + const code = token.substring(1, token.length - 1); + return {code}; + } + if (token.includes('\n')) { + return token + .split('\n') + .map(item => [item,
]); + } + return token; + }); + return [...values,
]; }); }; diff --git a/src-docs/src/services/playground/props.js b/src-docs/src/services/playground/props.js index 99aa24b9e59f..363344029edd 100644 --- a/src-docs/src/services/playground/props.js +++ b/src-docs/src/services/playground/props.js @@ -7,7 +7,7 @@ const getProp = prop => { newProp.custom = { origin: prop }; switch (prop.type.name) { - case 'bool': + case 'boolean': newProp.type = PropTypes.Boolean; if (prop.defaultValue) { newProp.defaultValue = prop.defaultValue.value === 'true'; diff --git a/src-docs/src/views/accessibility/accessibility_example.js b/src-docs/src/views/accessibility/accessibility_example.js index 36d1c47dcf27..528af5e3d2cf 100644 --- a/src-docs/src/views/accessibility/accessibility_example.js +++ b/src-docs/src/views/accessibility/accessibility_example.js @@ -10,6 +10,7 @@ import { EuiLink, EuiKeyboardAccessible, EuiSkipLink, + EuiScreenReaderOnly, } from '../../../../src/components'; import KeyboardAccessible from './keyboard_accessible'; @@ -48,8 +49,6 @@ const skipLinkSnippet = [ `, ]; -import { ScreenReaderOnlyDocsComponent } from './props'; - export const AccessibilityExample = { title: 'Accessibility', sections: [ @@ -117,7 +116,7 @@ export const AccessibilityExample = { ), props: { - EuiScreenReaderOnly: ScreenReaderOnlyDocsComponent, + EuiScreenReaderOnly, }, snippet: screenReaderOnlySnippet, demo: , diff --git a/src-docs/src/views/accessibility/props.tsx b/src-docs/src/views/accessibility/props.tsx deleted file mode 100644 index 64613ba0ccf0..000000000000 --- a/src-docs/src/views/accessibility/props.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import { EuiScreenReaderOnlyProps } from '../../../../src/components/accessibility/screen_reader'; - -export const ScreenReaderOnlyDocsComponent: FunctionComponent = () => ( -
-); diff --git a/src-docs/src/views/overlay_mask/overlay_mask_example.js b/src-docs/src/views/overlay_mask/overlay_mask_example.js index a5116302f769..999c70414d46 100644 --- a/src-docs/src/views/overlay_mask/overlay_mask_example.js +++ b/src-docs/src/views/overlay_mask/overlay_mask_example.js @@ -5,7 +5,9 @@ import { renderToHtml } from '../../services'; import { GuideSectionTypes } from '../../components'; -import { EuiOverlayMask, EuiCode } from '../../../../src/components'; +import { EuiCode } from '../../../../src/components'; + +import { EuiOverlayMaskProps } from './props'; import OverlayMask from './overlay_mask'; const overlayMaskSource = require('!!raw-loader!./overlay_mask'); @@ -50,7 +52,7 @@ export const OverlayMaskExample = {

), - props: { EuiOverlayMask }, + props: { EuiOverlayMask: EuiOverlayMaskProps }, snippet: ` {}}> `, @@ -93,7 +95,7 @@ export const OverlayMaskExample = {

), - props: { EuiOverlayMask }, + props: { EuiOverlayMask: EuiOverlayMaskProps }, snippet: ` `, demo: , diff --git a/src-docs/src/views/overlay_mask/props.tsx b/src-docs/src/views/overlay_mask/props.tsx new file mode 100644 index 000000000000..06aaead948bc --- /dev/null +++ b/src-docs/src/views/overlay_mask/props.tsx @@ -0,0 +1,21 @@ +import React, { FunctionComponent, ReactNode } from 'react'; +import { CommonProps } from '../../../../src/components/common'; + +interface EuiOverlayMaskInterface extends CommonProps { + /** + * Function that applies to clicking the mask itself and not the children + */ + onClick?: () => void; + /** + * ReactNode to render as this component's children + */ + children?: ReactNode; + /** + * Should the mask visually sit above or below the EuiHeader (controlled by z-index) + */ + headerZindexLocation?: 'above' | 'below'; +} + +export const EuiOverlayMaskProps: FunctionComponent = () => ( +
+); diff --git a/src/components/accessibility/keyboard_accessible.ts b/src/components/accessibility/keyboard_accessible.ts index 1a8a2ac7b710..ffcec6c17005 100644 --- a/src/components/accessibility/keyboard_accessible.ts +++ b/src/components/accessibility/keyboard_accessible.ts @@ -44,7 +44,10 @@ import { Component, cloneElement, KeyboardEvent, ReactElement } from 'react'; import { keys } from '../../services'; interface Props { - children: ReactElement; + /** + * ReactNode to render as this component's children + */ + children: ReactElement; } export class EuiKeyboardAccessible extends Component { diff --git a/src/components/accessibility/screen_reader.tsx b/src/components/accessibility/screen_reader.tsx index ad7a0d8e4e4c..1279f19b7c5d 100644 --- a/src/components/accessibility/screen_reader.tsx +++ b/src/components/accessibility/screen_reader.tsx @@ -21,6 +21,9 @@ import { cloneElement, ReactElement, FunctionComponent } from 'react'; import classNames from 'classnames'; export interface EuiScreenReaderOnlyProps { + /** + * ReactElement to render as this component's content + */ children: ReactElement; /** diff --git a/src/components/accordion/accordion.tsx b/src/components/accordion/accordion.tsx index f48789428295..c9efe33a008a 100644 --- a/src/components/accordion/accordion.tsx +++ b/src/components/accordion/accordion.tsx @@ -37,8 +37,8 @@ const paddingSizeToClassNameMap = { export const PADDING_SIZES = keysOf(paddingSizeToClassNameMap); export type EuiAccordionSize = keyof typeof paddingSizeToClassNameMap; -export type EuiAccordionProps = HTMLAttributes & - CommonProps & { +export type EuiAccordionProps = CommonProps & + Omit, 'id'> & { id: string; /** * Class that will apply to the trigger for the accordion. diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx index bbba7e76ac07..90aeb49be789 100644 --- a/src/components/avatar/avatar.tsx +++ b/src/components/avatar/avatar.tsx @@ -43,7 +43,7 @@ const typeToClassNameMap = { export const TYPES = keysOf(typeToClassNameMap); export type EuiAvatarType = keyof typeof typeToClassNameMap; -export type EuiAvatarProps = HTMLAttributes & +export type EuiAvatarProps = Omit, 'color'> & CommonProps & { /** * Full name of avatar for title attribute and calculating initial if not provided diff --git a/src/components/badge/badge.tsx b/src/components/badge/badge.tsx index 9c212cf4112d..3725662e986e 100644 --- a/src/components/badge/badge.tsx +++ b/src/components/badge/badge.tsx @@ -55,7 +55,7 @@ type WithAnchorProps = { href: string; target?: string; rel?: string; -} & Omit, 'href' | 'color'>; +} & Omit, 'href' | 'color' | 'onClick'>; type WithSpanProps = Omit, 'onClick' | 'color'>; diff --git a/src/components/badge/badge_group/badge_group.tsx b/src/components/badge/badge_group/badge_group.tsx index 35331f026e11..b38909f691ef 100644 --- a/src/components/badge/badge_group/badge_group.tsx +++ b/src/components/badge/badge_group/badge_group.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { HTMLAttributes, Ref, ReactNode } from 'react'; +import React, { forwardRef, HTMLAttributes, Ref, ReactNode } from 'react'; import classNames from 'classnames'; import { CommonProps, keysOf } from '../../common'; @@ -42,7 +42,7 @@ export interface EuiBadgeGroupProps { children?: ReactNode; } -export const EuiBadgeGroup = React.forwardRef< +export const EuiBadgeGroup = forwardRef< HTMLDivElement, CommonProps & HTMLAttributes & EuiBadgeGroupProps >( diff --git a/src/components/badge/beta_badge/beta_badge.tsx b/src/components/badge/beta_badge/beta_badge.tsx index bba4e4a90aea..4e189a859c32 100644 --- a/src/components/badge/beta_badge/beta_badge.tsx +++ b/src/components/badge/beta_badge/beta_badge.tsx @@ -75,7 +75,7 @@ type BadgeProps = { } & ExclusiveUnion; type EuiBetaBadgeProps = CommonProps & - HTMLAttributes & + Omit, 'title'> & BadgeProps; export const EuiBetaBadge: FunctionComponent = ({ diff --git a/src/components/badge/notification_badge/badge_notification.tsx b/src/components/badge/notification_badge/badge_notification.tsx index f895872e64da..42c48406060a 100644 --- a/src/components/badge/notification_badge/badge_notification.tsx +++ b/src/components/badge/notification_badge/badge_notification.tsx @@ -40,6 +40,9 @@ export type BadgeNotificationSize = keyof typeof sizeToClassNameMap; export interface EuiNotificationBadgeProps extends CommonProps, Omit, 'color'> { + /** + * ReactNode to render as this component's content + */ children: ReactNode; size?: BadgeNotificationSize; color?: BadgeNotificationColor; diff --git a/src/components/breadcrumbs/breadcrumbs.tsx b/src/components/breadcrumbs/breadcrumbs.tsx index 2ce3b6343112..c3140904c265 100644 --- a/src/components/breadcrumbs/breadcrumbs.tsx +++ b/src/components/breadcrumbs/breadcrumbs.tsx @@ -62,7 +62,10 @@ export type EuiBreadcrumbsProps = CommonProps & { * Hides extra (above the max) breadcrumbs under a collapsed item as the window gets smaller. * Pass a custom #EuiBreadcrumbResponsiveMaxCount object to change the number of breadcrumbs to show at the particular breakpoints. * Omitting or passing a `0` value will show all breadcrumbs. - * Pass `false` to turn this behavior off + * + * Pass `false` to turn this behavior off. + * + * Default: `{ xs: 1, s: 2, m: 4 }` */ responsive?: boolean | EuiBreadcrumbResponsiveMaxCount; diff --git a/src/components/button/button_icon/button_icon.tsx b/src/components/button/button_icon/button_icon.tsx index e3a6174da944..4473f7e42482 100644 --- a/src/components/button/button_icon/button_icon.tsx +++ b/src/components/button/button_icon/button_icon.tsx @@ -58,14 +58,18 @@ export interface EuiButtonIconProps extends CommonProps { iconSize?: IconSize; } -type EuiButtonIconPropsForAnchor = PropsForAnchor< +type EuiButtonIconPropsForAnchor = { + type?: string; +} & PropsForAnchor< EuiButtonIconProps, { buttonRef?: Ref; } >; -export type EuiButtonIconPropsForButton = PropsForButton< +export type EuiButtonIconPropsForButton = { + type?: 'submit' | 'reset' | 'button'; +} & PropsForButton< EuiButtonIconProps, { buttonRef?: Ref; diff --git a/src/components/button/button_toggle/button_toggle.tsx b/src/components/button/button_toggle/button_toggle.tsx index ac995590a8cc..cf086c907308 100644 --- a/src/components/button/button_toggle/button_toggle.tsx +++ b/src/components/button/button_toggle/button_toggle.tsx @@ -68,14 +68,17 @@ export interface EuiButtonToggleProps extends EuiButtonProps, CommonProps { } type EuiButtonTogglePropsForAnchor = EuiButtonToggleProps & - Omit, 'name'> & { + Omit, 'name' | 'href' | 'onClick'> & { href?: string; name?: string; onClick?: MouseEventHandler; }; type EuiButtonTogglePropsForButtonToggle = EuiButtonToggleProps & - Omit, 'name'> & { + Omit< + ButtonHTMLAttributes, + 'name' | 'onClick' | 'value' + > & { onClick?: MouseEventHandler; name?: string; value?: string; diff --git a/src/components/call_out/call_out.tsx b/src/components/call_out/call_out.tsx index 35322ecceea3..519cf719e87e 100644 --- a/src/components/call_out/call_out.tsx +++ b/src/components/call_out/call_out.tsx @@ -31,7 +31,7 @@ type Size = 's' | 'm'; type Heading = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'; export type EuiCallOutProps = CommonProps & - Omit, 'title'> & { + Omit, 'title' | 'color'> & { title?: ReactNode; iconType?: IconType; color?: Color; diff --git a/src/components/card/checkable_card/checkable_card.tsx b/src/components/card/checkable_card/checkable_card.tsx index a66a6730e30e..c0c4e96fcaf1 100644 --- a/src/components/card/checkable_card/checkable_card.tsx +++ b/src/components/card/checkable_card/checkable_card.tsx @@ -47,9 +47,11 @@ interface EuiCheckableCardAsCheckboxProps checkableType: 'checkbox'; } -export type EuiCheckableCardProps = EuiCheckableCardBaseProps & - (EuiCheckableCardAsCheckboxProps | EuiCheckableCardAsRadioProps); - +export type EuiCheckableCardProps = Omit< + EuiCheckableCardAsCheckboxProps | EuiCheckableCardAsRadioProps, + 'label' | 'id' +> & + EuiCheckableCardBaseProps; export const EuiCheckableCard: FunctionComponent = ({ children, className, diff --git a/src/components/collapsible_nav/collapsible_nav.tsx b/src/components/collapsible_nav/collapsible_nav.tsx index c9b5f745d0e5..65e5e2d19412 100644 --- a/src/components/collapsible_nav/collapsible_nav.tsx +++ b/src/components/collapsible_nav/collapsible_nav.tsx @@ -38,6 +38,9 @@ import { EuiScreenReaderOnly } from '../accessibility'; export type EuiCollapsibleNavProps = CommonProps & HTMLAttributes & { + /** + * ReactNode to render as this component's content + */ children?: ReactNode; /** * Keeps navigation flyout visible and push `` content via padding diff --git a/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.tsx b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.tsx index cd884e5e6cad..07751fc3b150 100644 --- a/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.tsx +++ b/src/components/collapsible_nav/collapsible_nav_group/collapsible_nav_group.tsx @@ -43,6 +43,9 @@ export const BACKGROUNDS = Object.keys( ) as Background[]; export interface EuiCollapsibleNavGroupInterface extends CommonProps { + /** + * ReactNode to render as this component's content + */ children?: ReactNode; /** * Sits left of the `title` and only when `title` is present diff --git a/src/components/common.ts b/src/components/common.ts index 7bf2037e7aca..47a81bd83d0c 100644 --- a/src/components/common.ts +++ b/src/components/common.ts @@ -182,16 +182,16 @@ export type ExclusiveUnion = T | U extends object // if there are any shar * type ComponentProps = ExlcusiveUnion * const Component: FunctionComponent ... */ -export type PropsForAnchor = T & - AnchorHTMLAttributes & { - href?: string; - onClick?: MouseEventHandler; - } & P; - -export type PropsForButton = T & - ButtonHTMLAttributes & { - onClick?: MouseEventHandler; - } & P; +export type PropsForAnchor = T & { + href?: string; + onClick?: MouseEventHandler; +} & AnchorHTMLAttributes & + P; + +export type PropsForButton = T & { + onClick?: MouseEventHandler; +} & ButtonHTMLAttributes & + P; /** * Replaces all properties on any type as optional, includes nested types diff --git a/src/components/context/context.tsx b/src/components/context/context.tsx index abd0a6cd57a0..dd0ffa57169d 100644 --- a/src/components/context/context.tsx +++ b/src/components/context/context.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { createContext, ReactChild } from 'react'; +import React, { createContext, ReactChild, ReactNode } from 'react'; export interface RenderableValues { [key: string]: ReactChild; @@ -40,7 +40,10 @@ const { Provider: EuiI18nProvider, Consumer: EuiI18nConsumer } = I18nContext; interface EuiContextProps { i18n: I18nShape; - children: React.ReactNode; + /** + * ReactNode to render as this component's content + */ + children: ReactNode; } const EuiContext: React.FunctionComponent = ({ diff --git a/src/components/context_menu/context_menu_item.tsx b/src/components/context_menu/context_menu_item.tsx index 294b9381a5cb..ef45779ce76f 100644 --- a/src/components/context_menu/context_menu_item.tsx +++ b/src/components/context_menu/context_menu_item.tsx @@ -66,7 +66,10 @@ export interface EuiContextMenuItemProps extends CommonProps { } type Props = CommonProps & - Omit, 'type'> & + Omit< + ButtonHTMLAttributes, + 'type' | 'onClick' | 'disabled' + > & EuiContextMenuItemProps; const layoutAlignToClassNames: { diff --git a/src/components/context_menu/context_menu_panel.tsx b/src/components/context_menu/context_menu_panel.tsx index 2cd6fd639a31..c709d62a248b 100644 --- a/src/components/context_menu/context_menu_panel.tsx +++ b/src/components/context_menu/context_menu_panel.tsx @@ -59,7 +59,7 @@ export interface EuiContextMenuPanelProps { type Props = CommonProps & Omit< HTMLAttributes, - 'onKeyDown' | 'tabIndex' | 'onAnimationEnd' + 'onKeyDown' | 'tabIndex' | 'onAnimationEnd' | 'title' > & EuiContextMenuPanelProps; diff --git a/src/components/control_bar/control_bar.tsx b/src/components/control_bar/control_bar.tsx index 4e7e7ddb8190..17595544dec3 100644 --- a/src/components/control_bar/control_bar.tsx +++ b/src/components/control_bar/control_bar.tsx @@ -24,6 +24,7 @@ import React, { HTMLAttributes, MouseEventHandler, Ref, + ReactNode, } from 'react'; import { EuiScreenReaderOnly } from '../accessibility'; import { EuiBreadcrumbs, EuiBreadcrumbsProps } from '../breadcrumbs'; @@ -49,7 +50,7 @@ import { EuiPortal } from '../portal'; */ export interface ButtonControl extends Omit { id: string; - label: React.ReactNode; + label: ReactNode; } type ButtonPropsForAnchor = PropsForAnchor< @@ -78,12 +79,13 @@ type ButtonControlProps = ExclusiveUnion< * Requires `label` as the `children`. * `onClick` must be provided to handle the content swapping. */ -export type TabControl = ButtonHTMLAttributes & { +export interface TabControl + extends Omit, 'id' | 'onClick'> { controlType: 'tab'; id: string; - label: React.ReactNode; - onClick: React.MouseEventHandler; -}; + label: ReactNode; + onClick: MouseEventHandler; +} /** * Extends EuiBreadcrumbs @@ -102,7 +104,7 @@ export interface TextControl HTMLAttributes { controlType: 'text'; id: string; - text: React.ReactNode; + text: ReactNode; } export interface SpacerControl { @@ -449,9 +451,7 @@ export class EuiControlBar extends Component< ref={node => { this.bar = node; }}> - {controls.map((control, index) => { - return controlItem(control, index); - })} + {controls.map((control, index) => controlItem(control, index))}
{this.props.showContent ? (
{children}
diff --git a/src/components/copy/copy.tsx b/src/components/copy/copy.tsx index 83d267c69276..ab9c9fcd0b74 100644 --- a/src/components/copy/copy.tsx +++ b/src/components/copy/copy.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { ReactElement, ReactNode } from 'react'; +import React, { Component, ReactElement, ReactNode } from 'react'; import { CommonProps } from '../common'; import { copyToClipboard } from '../../services'; import { EuiToolTip, EuiToolTipProps } from '../tool_tip'; @@ -49,7 +49,7 @@ interface EuiCopyState { tooltipText: ReactNode; } -export class EuiCopy extends React.Component { +export class EuiCopy extends Component { static defaultProps = { afterMessage: 'Copied', }; diff --git a/src/components/drag_and_drop/drag_drop_context.tsx b/src/components/drag_and_drop/drag_drop_context.tsx index 27025db973b8..177b765829bf 100644 --- a/src/components/drag_and_drop/drag_drop_context.tsx +++ b/src/components/drag_and_drop/drag_drop_context.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { FunctionComponent, useState } from 'react'; +import React, { FunctionComponent, useState, createContext } from 'react'; import { DragDropContext, DragDropContextProps, @@ -34,9 +34,7 @@ interface EuiDraggingContext { isDraggingType: EuiDraggingType; } -export const EuiDragDropContextContext = React.createContext< - EuiDraggingContext ->({ +export const EuiDragDropContextContext = createContext({ isDraggingType: null, }); diff --git a/src/components/drag_and_drop/draggable.tsx b/src/components/drag_and_drop/draggable.tsx index 774845b956a5..ad6e40d21f02 100644 --- a/src/components/drag_and_drop/draggable.tsx +++ b/src/components/drag_and_drop/draggable.tsx @@ -42,6 +42,9 @@ export type EuiDraggableSpacing = keyof typeof spacingToClassNameMap; export interface EuiDraggableProps extends CommonProps, Omit { + /** + * ReactNode to render as this component's content + */ children: ReactElement | DraggableProps['children']; className?: string; /** diff --git a/src/components/drag_and_drop/droppable.tsx b/src/components/drag_and_drop/droppable.tsx index b06060e75d2d..28bd2e150dd5 100644 --- a/src/components/drag_and_drop/droppable.tsx +++ b/src/components/drag_and_drop/droppable.tsx @@ -40,6 +40,9 @@ export type EuiDroppableSpacing = keyof typeof spacingToClassNameMap; export interface EuiDroppableProps extends CommonProps, Omit { + /** + * ReactNode to render as this component's content + */ children: ReactElement | ReactElement[] | DroppableProps['children']; className?: string; /** diff --git a/src/components/error_boundary/error_boundary.tsx b/src/components/error_boundary/error_boundary.tsx index 6a983e029fa0..9436aae5e494 100644 --- a/src/components/error_boundary/error_boundary.tsx +++ b/src/components/error_boundary/error_boundary.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { Component, HTMLAttributes } from 'react'; +import React, { Component, HTMLAttributes, ReactNode } from 'react'; import { CommonProps } from '../common'; import PropTypes from 'prop-types'; @@ -29,7 +29,12 @@ interface EuiErrorBoundaryState { } export type EuiErrorBoundaryProps = CommonProps & - HTMLAttributes; + HTMLAttributes & { + /** + * ReactNode to render as this component's content + */ + children: ReactNode; + }; export class EuiErrorBoundary extends Component< EuiErrorBoundaryProps, diff --git a/src/components/facet/facet_button.tsx b/src/components/facet/facet_button.tsx index 104a93990294..350e3a5c047c 100644 --- a/src/components/facet/facet_button.tsx +++ b/src/components/facet/facet_button.tsx @@ -34,8 +34,13 @@ import { EuiNotificationBadge } from '../badge'; import { EuiLoadingSpinner } from '../loading'; import { EuiInnerText } from '../inner_text'; -export interface EuiFacetButtonProps { +export interface EuiFacetButtonProps + extends CommonProps, + Omit, 'onClick'> { buttonRef?: RefCallback; + /** + * ReactNode to render as this component's content + */ children: ReactNode; /** * Any node, but preferrably a `EuiIcon` or `EuiAvatar` @@ -57,9 +62,7 @@ export interface EuiFacetButtonProps { quantity?: number; } -export const EuiFacetButton: FunctionComponent & - EuiFacetButtonProps> = ({ +export const EuiFacetButton: FunctionComponent = ({ children, className, icon, diff --git a/src/components/flex/flex_grid.tsx b/src/components/flex/flex_grid.tsx index b8e6202ff46a..6126717ee603 100644 --- a/src/components/flex/flex_grid.tsx +++ b/src/components/flex/flex_grid.tsx @@ -26,6 +26,9 @@ export type FlexGridColumns = 0 | 1 | 2 | 3 | 4; export type FlexGridDirection = keyof typeof directionToClassNameMap; export interface EuiFlexGridProps { + /** + * ReactNode to render as this component's content + */ children?: ReactNode; /** * Number of columns `1-4`, pass `0` for normal display diff --git a/src/components/flex/flex_group.tsx b/src/components/flex/flex_group.tsx index f009ae2b65ae..72c1fa8f5161 100644 --- a/src/components/flex/flex_group.tsx +++ b/src/components/flex/flex_group.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { HTMLAttributes, Ref } from 'react'; +import React, { HTMLAttributes, Ref, forwardRef } from 'react'; import classNames from 'classnames'; import { CommonProps, keysOf } from '../common'; @@ -85,7 +85,7 @@ const isValidElement = ( return ['div', 'span'].includes(component); }; -const EuiFlexGroup = React.forwardRef< +export const EuiFlexGroup = forwardRef< HTMLDivElement | HTMLSpanElement, CommonProps & HTMLAttributes & @@ -143,5 +143,3 @@ const EuiFlexGroup = React.forwardRef< } ); EuiFlexGroup.displayName = 'EuiFlexGroup'; - -export { EuiFlexGroup }; diff --git a/src/components/form/field_number/field_number.tsx b/src/components/form/field_number/field_number.tsx index 8c407ee9e74b..c42ac2006091 100644 --- a/src/components/form/field_number/field_number.tsx +++ b/src/components/form/field_number/field_number.tsx @@ -30,7 +30,10 @@ import { EuiValidatableControl } from '../validatable_control'; import { IconType } from '../../icon'; -export type EuiFieldNumberProps = InputHTMLAttributes & +export type EuiFieldNumberProps = Omit< + InputHTMLAttributes, + 'min' | 'max' | 'readOnly' | 'step' +> & CommonProps & { icon?: IconType; isInvalid?: boolean; diff --git a/src/components/form/field_password/field_password.tsx b/src/components/form/field_password/field_password.tsx index 502443aded98..a6ffac4da709 100644 --- a/src/components/form/field_password/field_password.tsx +++ b/src/components/form/field_password/field_password.tsx @@ -36,7 +36,10 @@ import { EuiButtonIcon, EuiButtonIconProps } from '../../button'; import { useEuiI18n } from '../../i18n'; import { useCombinedRefs } from '../../../services'; -export type EuiFieldPasswordProps = InputHTMLAttributes & +export type EuiFieldPasswordProps = Omit< + InputHTMLAttributes, + 'value' +> & CommonProps & { isInvalid?: boolean; fullWidth?: boolean; @@ -55,6 +58,7 @@ export type EuiFieldPasswordProps = InputHTMLAttributes & * `string` | `ReactElement` or an array of these */ append?: EuiFormControlLayoutProps['append']; + value?: string | number; /** * Change the `type` of input for manually handling obfuscation. diff --git a/src/components/form/form_fieldset/form_legend.tsx b/src/components/form/form_fieldset/form_legend.tsx index 1edd66513ccb..ce7290fde1a1 100644 --- a/src/components/form/form_fieldset/form_legend.tsx +++ b/src/components/form/form_fieldset/form_legend.tsx @@ -24,6 +24,9 @@ import { EuiScreenReaderOnly } from '../../accessibility'; export type EuiFormLegendProps = HTMLAttributes & CommonProps & { + /** + * ReactNode to render as this component's content + */ children: ReactNode; /** * For a hidden legend that is still visible to the screen reader, set to 'hidden' diff --git a/src/components/form/form_row/form_row.tsx b/src/components/form/form_row/form_row.tsx index 19ddfda8f6be..1d08b1434e43 100644 --- a/src/components/form/form_row/form_row.tsx +++ b/src/components/form/form_row/form_row.tsx @@ -76,6 +76,9 @@ type EuiFormRowCommonProps = CommonProps & { * Escape hatch to not render duplicate labels if the child also renders a label */ hasChildLabel?: boolean; + /** + * ReactElement to render as this component's content + */ children: ReactElement; label?: ReactNode; /** diff --git a/src/components/form/radio/radio.tsx b/src/components/form/radio/radio.tsx index d77ca7ed6d7a..c01c4500e99e 100644 --- a/src/components/form/radio/radio.tsx +++ b/src/components/form/radio/radio.tsx @@ -49,7 +49,7 @@ interface withId extends RadioProps { } export type EuiRadioProps = CommonProps & - Omit, 'onChange'> & + Omit, 'onChange' | 'id'> & ExclusiveUnion, withId>; export const EuiRadio: FunctionComponent = ({ diff --git a/src/components/form/range/range_label.tsx b/src/components/form/range/range_label.tsx index d5311c024ff5..902a1b677838 100644 --- a/src/components/form/range/range_label.tsx +++ b/src/components/form/range/range_label.tsx @@ -21,6 +21,9 @@ import React, { FunctionComponent } from 'react'; import classNames from 'classnames'; export interface EuiRangeLabelProps { + /** + * ReactNode to render as this component's content + */ children: string | number; disabled?: boolean; side?: 'min' | 'max'; diff --git a/src/components/form/switch/switch.tsx b/src/components/form/switch/switch.tsx index 125b018d3a33..7be319f7ba41 100644 --- a/src/components/form/switch/switch.tsx +++ b/src/components/form/switch/switch.tsx @@ -39,7 +39,10 @@ export type EuiSwitchEvent = React.BaseSyntheticEvent< >; export type EuiSwitchProps = CommonProps & - Omit, 'onChange'> & { + Omit< + ButtonHTMLAttributes, + 'onChange' | 'type' | 'disabled' + > & { /** * Whether to render the render the text label */ @@ -52,6 +55,7 @@ export type EuiSwitchProps = CommonProps & onChange: (event: EuiSwitchEvent) => void; disabled?: boolean; compressed?: boolean; + type?: 'submit' | 'reset' | 'button'; }; export const EuiSwitch: FunctionComponent = ({ diff --git a/src/components/form/validatable_control/validatable_control.tsx b/src/components/form/validatable_control/validatable_control.tsx index c5e451cd1895..110b9e31d4e5 100644 --- a/src/components/form/validatable_control/validatable_control.tsx +++ b/src/components/form/validatable_control/validatable_control.tsx @@ -43,6 +43,9 @@ function isMutableRef( export interface EuiValidatableControlProps { isInvalid?: boolean; + /** + * ReactNode to render as this component's content + */ children: ReactElementWithRef; } diff --git a/src/components/header/header_logo.tsx b/src/components/header/header_logo.tsx index 6fcc883dbc82..ab6393b9709a 100644 --- a/src/components/header/header_logo.tsx +++ b/src/components/header/header_logo.tsx @@ -35,6 +35,9 @@ export type EuiHeaderLogoProps = CommonProps & target?: string; iconType?: IconType; iconTitle?: string; + /** + * ReactNode to render as this component's content + */ children?: ReactNode; }; diff --git a/src/components/header/header_section/header_section_item.tsx b/src/components/header/header_section/header_section_item.tsx index 63195b57e356..9e16e24f414e 100644 --- a/src/components/header/header_section/header_section_item.tsx +++ b/src/components/header/header_section/header_section_item.tsx @@ -36,6 +36,9 @@ export type EuiHeaderSectionItemProps = CommonProps & { * Not supported in Amsterdam theme. */ border?: Border; + /** + * ReactNode to render as this component's content + */ children?: ReactNode; }; diff --git a/src/components/highlight/highlight.tsx b/src/components/highlight/highlight.tsx index 168b1f0e5296..20e7dc7f57d8 100644 --- a/src/components/highlight/highlight.tsx +++ b/src/components/highlight/highlight.tsx @@ -37,6 +37,9 @@ interface EuiHighlightChunk { export type EuiHighlightProps = HTMLAttributes & CommonProps & { + /** + * string to highlight as this component's content + */ children: string; /** diff --git a/src/components/i18n/i18n_number.tsx b/src/components/i18n/i18n_number.tsx index bfae9d7691b7..a132b016ee08 100644 --- a/src/components/i18n/i18n_number.tsx +++ b/src/components/i18n/i18n_number.tsx @@ -33,6 +33,9 @@ interface EuiI18nNumberValueShape { interface EuiI18nNumberValuesShape { values: number[]; + /** + * ReactNode to render as this component's content + */ children: (x: ReactChild[]) => ReactElement; } diff --git a/src/components/inner_text/inner_text.tsx b/src/components/inner_text/inner_text.tsx index 982c180805af..eedded2f5f60 100644 --- a/src/components/inner_text/inner_text.tsx +++ b/src/components/inner_text/inner_text.tsx @@ -72,6 +72,9 @@ export function useInnerText( } export interface EuiInnerTextProps { + /** + * ReactNode to render as this component's content + */ children: (ref?: (node: RefT) => void, innerText?: string) => ReactElement; fallback?: string; } diff --git a/src/components/key_pad_menu/key_pad_menu_item.tsx b/src/components/key_pad_menu/key_pad_menu_item.tsx index 403a055e88e8..b94347a747fd 100644 --- a/src/components/key_pad_menu/key_pad_menu_item.tsx +++ b/src/components/key_pad_menu/key_pad_menu_item.tsx @@ -60,6 +60,9 @@ const renderContent = ( ); interface EuiKeyPadMenuItemCommonProps { + /** + * ReactNode to render as this component's content + */ children: ReactNode; isDisabled?: boolean; label: ReactNode; @@ -85,8 +88,8 @@ interface EuiKeyPadMenuItemCommonProps { export type EuiKeyPadMenuItemProps = CommonProps & ExclusiveUnion< - AnchorHTMLAttributes, - ButtonHTMLAttributes + Omit, 'onClick'>, + Omit, 'onClick'> > & EuiKeyPadMenuItemCommonProps; diff --git a/src/components/link/link.test.tsx b/src/components/link/link.test.tsx index d52c201a57e4..d291391f2276 100644 --- a/src/components/link/link.test.tsx +++ b/src/components/link/link.test.tsx @@ -23,11 +23,6 @@ import { requiredProps } from '../../test'; import { EuiLink, COLORS } from './link'; describe('EuiLink', () => { - test('it errors if an invalid color is provided', () => { - // @ts-ignore as we're deliberately using a bogus value - expect(() => render()).toThrow(/phooey/); - }); - COLORS.forEach(color => { test(`${color} is rendered`, () => { const component = render(); diff --git a/src/components/link/link.tsx b/src/components/link/link.tsx index 4298f0fe8fed..e9aa725db588 100644 --- a/src/components/link/link.tsx +++ b/src/components/link/link.tsx @@ -18,6 +18,7 @@ */ import React, { + forwardRef, AnchorHTMLAttributes, ButtonHTMLAttributes, MouseEventHandler, @@ -58,9 +59,10 @@ export interface LinkButtonProps { onClick?: MouseEventHandler; } -export type EuiLinkButtonProps = CommonProps & - ButtonHTMLAttributes & - LinkButtonProps; +export interface EuiLinkButtonProps + extends CommonProps, + Omit, 'type' | 'color' | 'onClick'>, + LinkButtonProps {} export interface LinkAnchorProps { type?: EuiLinkType; @@ -71,19 +73,19 @@ export interface LinkAnchorProps { external?: boolean; } -export type EuiLinkAnchorProps = CommonProps & - AnchorHTMLAttributes & - LinkAnchorProps; +export interface EuiLinkAnchorProps + extends CommonProps, + Omit, 'type' | 'color' | 'onClick'>, + LinkAnchorProps { + onClick?: MouseEventHandler; +} export type EuiLinkProps = ExclusiveUnion< EuiLinkButtonProps, EuiLinkAnchorProps >; -const EuiLink = React.forwardRef< - HTMLAnchorElement | HTMLButtonElement, - EuiLinkProps ->( +const EuiLink = forwardRef( ( { children, diff --git a/src/components/list_group/list_group.tsx b/src/components/list_group/list_group.tsx index 65be3f72ec53..b945c9eb376f 100644 --- a/src/components/list_group/list_group.tsx +++ b/src/components/list_group/list_group.tsx @@ -34,7 +34,7 @@ export const GUTTER_SIZES = Object.keys( ) as GutterSize[]; export type EuiListGroupProps = CommonProps & - HTMLAttributes & { + Omit, 'color'> & { /** * Add a border to the list container */ diff --git a/src/components/list_group/list_group_item.tsx b/src/components/list_group/list_group_item.tsx index 3176a7ad0647..b8be3d864ce5 100644 --- a/src/components/list_group/list_group_item.tsx +++ b/src/components/list_group/list_group_item.tsx @@ -57,12 +57,15 @@ const colorToClassNameMap: { [color in Color]: string } = { export const COLORS = Object.keys(colorToClassNameMap) as Color[]; export type EuiListGroupItemProps = CommonProps & - ExclusiveUnion< + Omit< ExclusiveUnion< - ButtonHTMLAttributes, - Omit, 'href'> + ExclusiveUnion< + ButtonHTMLAttributes, + Omit, 'href'> + >, + HTMLAttributes >, - HTMLAttributes + 'onClick' | 'color' | 'target' | 'rel' > & { /** * Size of the label text diff --git a/src/components/mark/mark.tsx b/src/components/mark/mark.tsx index a2be70823352..bd127be8a779 100644 --- a/src/components/mark/mark.tsx +++ b/src/components/mark/mark.tsx @@ -22,6 +22,9 @@ import { CommonProps } from '../common'; import classNames from 'classnames'; export type EuiMarkProps = HTMLAttributes & CommonProps & { + /** + * ReactNode to render as this component's content + */ children: string; }; diff --git a/src/components/modal/confirm_modal.tsx b/src/components/modal/confirm_modal.tsx index f1d3519ee395..6ed122383b7a 100644 --- a/src/components/modal/confirm_modal.tsx +++ b/src/components/modal/confirm_modal.tsx @@ -40,6 +40,9 @@ export interface EuiConfirmModalProps EuiModalProps, 'children' | 'initialFocus' | 'onClose' | 'title' > { + /** + * ReactNode to render as this component's content + */ children?: ReactNode; title?: ReactNode; cancelButtonText?: ReactNode; diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx index 417eef79d328..74287dc432da 100644 --- a/src/components/modal/modal.tsx +++ b/src/components/modal/modal.tsx @@ -30,6 +30,9 @@ import { EuiI18n } from '../i18n'; export interface EuiModalProps extends HTMLAttributes { className?: string; + /** + * ReactNode to render as this component's content + */ children: ReactNode; onClose: ( event?: diff --git a/src/components/nav_drawer/nav_drawer.tsx b/src/components/nav_drawer/nav_drawer.tsx index b20b1b7a5561..f7e4785c9188 100644 --- a/src/components/nav_drawer/nav_drawer.tsx +++ b/src/components/nav_drawer/nav_drawer.tsx @@ -44,6 +44,9 @@ const MENU_ELEMENT_ID = 'navDrawerMenu'; export interface EuiNavDrawerProps extends CommonProps, HTMLAttributes { + /** + * One or more ReactNodes to render as this component's content + */ children?: ReactNode | ReactNode[]; /** diff --git a/src/components/observer/mutation_observer/mutation_observer.ts b/src/components/observer/mutation_observer/mutation_observer.ts index f8996f375405..4db7fe6050f4 100644 --- a/src/components/observer/mutation_observer/mutation_observer.ts +++ b/src/components/observer/mutation_observer/mutation_observer.ts @@ -22,6 +22,9 @@ import { ReactNode } from 'react'; import { EuiObserver } from '../observer'; interface Props { + /** + * ReactNode to render as this component's content + */ children: (ref: (e: HTMLElement | null) => void) => ReactNode; onMutation: MutationCallback; observerOptions?: MutationObserverInit; diff --git a/src/components/observer/observer.ts b/src/components/observer/observer.ts index 927ada17f0e7..f3c378c87fe2 100644 --- a/src/components/observer/observer.ts +++ b/src/components/observer/observer.ts @@ -20,6 +20,9 @@ import { Component, ReactNode } from 'react'; interface BaseProps { + /** + * ReactNode to render as this component's content + */ children: (ref: any) => ReactNode; } diff --git a/src/components/observer/resize_observer/resize_observer.tsx b/src/components/observer/resize_observer/resize_observer.tsx index 35a3c603a717..1d1d1edc78bb 100644 --- a/src/components/observer/resize_observer/resize_observer.tsx +++ b/src/components/observer/resize_observer/resize_observer.tsx @@ -21,6 +21,9 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; import { EuiObserver, Observer } from '../observer'; interface Props { + /** + * ReactNode to render as this component's content + */ children: (ref: (e: HTMLElement | null) => void) => ReactNode; onResize: (dimensions: { height: number; width: number }) => void; } diff --git a/src/components/outside_click_detector/outside_click_detector.ts b/src/components/outside_click_detector/outside_click_detector.ts index 98c64d8d7181..cbfc969b068d 100644 --- a/src/components/outside_click_detector/outside_click_detector.ts +++ b/src/components/outside_click_detector/outside_click_detector.ts @@ -32,6 +32,9 @@ export interface EuiEvent extends Event { } interface Props { + /** + * ReactNode to render as this component's content + */ children: ReactElement; onOutsideClick: (event: EuiEvent) => void; isDisabled?: boolean; diff --git a/src/components/overlay_mask/overlay_mask.tsx b/src/components/overlay_mask/overlay_mask.tsx index 981221cc7190..ac4e460f6cdd 100644 --- a/src/components/overlay_mask/overlay_mask.tsx +++ b/src/components/overlay_mask/overlay_mask.tsx @@ -39,6 +39,9 @@ export interface EuiOverlayMaskInterface { * Function that applies to clicking the mask itself and not the children */ onClick?: () => void; + /** + * ReactNode to render as this component's content + */ children?: ReactNode; /** * Should the mask visually sit above or below the EuiHeader (controlled by z-index) diff --git a/src/components/page/page_body/page_body.tsx b/src/components/page/page_body/page_body.tsx index 135833362a1a..b2fcf443d968 100644 --- a/src/components/page/page_body/page_body.tsx +++ b/src/components/page/page_body/page_body.tsx @@ -17,14 +17,14 @@ * under the License. */ -import React from 'react'; +import React, { PropsWithChildren, ComponentType, ComponentProps } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../../common'; -type ComponentType = keyof JSX.IntrinsicElements | React.ComponentType; +type ComponentTypes = keyof JSX.IntrinsicElements | ComponentType; -export type EuiPageBodyProps = CommonProps & - React.ComponentProps & { +export type EuiPageBodyProps = CommonProps & + ComponentProps & { /** * Sets the max-width of the page, * set to `true` to use the default size, @@ -39,14 +39,14 @@ export type EuiPageBodyProps = CommonProps & component?: T; }; -export const EuiPageBody = ({ +export const EuiPageBody = ({ children, restrictWidth = false, style, className, component: Component = 'main' as T, ...rest -}: React.PropsWithChildren>) => { +}: PropsWithChildren>) => { let widthClassname; let newStyle; diff --git a/src/components/portal/portal.tsx b/src/components/portal/portal.tsx index d29f445ba9fc..c262875cc4be 100644 --- a/src/components/portal/portal.tsx +++ b/src/components/portal/portal.tsx @@ -43,6 +43,9 @@ export const INSERT_POSITIONS: EuiPortalInsertPosition[] = keysOf( type EuiPortalInsertPosition = keyof typeof insertPositions; interface EuiPortalProps { + /** + * ReactNode to render as this component's content + */ children: React.ReactNode; insert?: { sibling: HTMLElement; position: EuiPortalInsertPosition }; portalRef?: (ref: HTMLDivElement | null) => void; diff --git a/src/components/progress/progress.tsx b/src/components/progress/progress.tsx index 5ad5921eb85f..f3b388e06ffa 100644 --- a/src/components/progress/progress.tsx +++ b/src/components/progress/progress.tsx @@ -79,7 +79,7 @@ export type EuiProgressProps = CommonProps & { type Indeterminate = EuiProgressProps & HTMLAttributes; type Determinate = EuiProgressProps & - ProgressHTMLAttributes & { + Omit, 'max'> & { max?: number; /* * If true, will render the percentage, otherwise pass a custom node diff --git a/src/components/resizable_container/context.tsx b/src/components/resizable_container/context.tsx index 1be0fbd052d2..27ea85a8f19d 100644 --- a/src/components/resizable_container/context.tsx +++ b/src/components/resizable_container/context.tsx @@ -77,6 +77,9 @@ interface ContextProps { const EuiResizablePanelContext = createContext({}); interface ContextProviderProps extends Required { + /** + * ReactNode to render as this component's content + */ children: any; } diff --git a/src/components/resizable_container/resizable_panel.tsx b/src/components/resizable_container/resizable_panel.tsx index 5026b7d053db..3baf24c25dbd 100644 --- a/src/components/resizable_container/resizable_panel.tsx +++ b/src/components/resizable_container/resizable_panel.tsx @@ -66,6 +66,9 @@ export interface EuiResizablePanelProps */ scrollable?: boolean; + /** + * ReactNode to render as this component's content + */ children: ReactNode; /** diff --git a/src/components/selectable/selectable.tsx b/src/components/selectable/selectable.tsx index df523aab0659..7363bfaa5911 100644 --- a/src/components/selectable/selectable.tsx +++ b/src/components/selectable/selectable.tsx @@ -67,11 +67,8 @@ type EuiSelectableSearchableProps = ExclusiveUnion< } >; -export type EuiSelectableProps = Omit< - HTMLAttributes, - 'children' | 'onChange' -> & - CommonProps & +export type EuiSelectableProps = CommonProps & + Omit, 'children' | 'onChange'> & EuiSelectableSearchableProps & { /** * Function that takes the `list` node and then diff --git a/src/components/side_nav/side_nav_item.tsx b/src/components/side_nav/side_nav_item.tsx index fc84f0ec289d..4549127e8ab7 100644 --- a/src/components/side_nav/side_nav_item.tsx +++ b/src/components/side_nav/side_nav_item.tsx @@ -62,6 +62,9 @@ interface GuaranteedRenderItemProps { rel?: string; onClick?: ItemProps['onClick']; className: string; + /** + * ReactNode to render as this component's content + */ children: ReactNode; } export type RenderItem = ( diff --git a/src/components/spacer/spacer.tsx b/src/components/spacer/spacer.tsx index b344dec3bc5d..8e3e574e7291 100644 --- a/src/components/spacer/spacer.tsx +++ b/src/components/spacer/spacer.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { HTMLAttributes } from 'react'; +import React, { FunctionComponent, HTMLAttributes } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../common'; @@ -40,7 +40,7 @@ export type EuiSpacerProps = HTMLAttributes & size?: SpacerSize; }; -export const EuiSpacer: React.FunctionComponent = ({ +export const EuiSpacer: FunctionComponent = ({ className, size = 'l', ...rest diff --git a/src/components/steps/step.tsx b/src/components/steps/step.tsx index 362166f0c914..055249b506e7 100644 --- a/src/components/steps/step.tsx +++ b/src/components/steps/step.tsx @@ -29,6 +29,9 @@ import { EuiStepStatus, EuiStepNumber } from './step_number'; import { EuiI18n } from '../i18n'; export interface EuiStepInterface { + /** + * ReactNode to render as this component's content + */ children: ReactNode; /** * The HTML tag used for the title @@ -50,7 +53,7 @@ export interface EuiStepInterface { } export type EuiStepProps = CommonProps & - HTMLAttributes & + Omit, 'title'> & EuiStepInterface; export const EuiStep: FunctionComponent = ({ diff --git a/src/components/table/table_header_cell.tsx b/src/components/table/table_header_cell.tsx index 6e20a5a44c12..d04fd2f4b384 100644 --- a/src/components/table/table_header_cell.tsx +++ b/src/components/table/table_header_cell.tsx @@ -41,7 +41,7 @@ import { EuiI18n } from '../i18n'; export type TableHeaderCellScope = 'col' | 'row' | 'colgroup' | 'rowgroup'; type Props = CommonProps & - ThHTMLAttributes & { + Omit, 'align' | 'scope'> & { align?: HorizontalAlignment; /** * Set `allowNeutralSort` on EuiInMemoryTable to false to force column diff --git a/src/components/tabs/tab.tsx b/src/components/tabs/tab.tsx index a742dfbff359..91bcdee1783f 100644 --- a/src/components/tabs/tab.tsx +++ b/src/components/tabs/tab.tsx @@ -33,13 +33,13 @@ export interface EuiTabProps extends CommonProps { } type EuiTabPropsForAnchor = EuiTabProps & - AnchorHTMLAttributes & { + Omit, 'onClick' | 'href'> & { href?: string; onClick?: MouseEventHandler; }; type EuiTabPropsForButton = EuiTabProps & - ButtonHTMLAttributes & { + Omit, 'onClick'> & { onClick?: MouseEventHandler; }; diff --git a/src/components/tabs/tabs.tsx b/src/components/tabs/tabs.tsx index 6462f76a7d02..ff9ba5eb8850 100644 --- a/src/components/tabs/tabs.tsx +++ b/src/components/tabs/tabs.tsx @@ -17,7 +17,12 @@ * under the License. */ -import React, { HTMLAttributes, PropsWithChildren } from 'react'; +import React, { + forwardRef, + HTMLAttributes, + PropsWithChildren, + ReactNode, +} from 'react'; import classNames from 'classnames'; import { CommonProps, keysOf } from '../common'; @@ -41,6 +46,10 @@ export type EuiTabsSizes = keyof typeof sizeToClassNameMap; export type EuiTabsProps = CommonProps & HTMLAttributes & { + /** + * ReactNode to render as this component's content + */ + children?: ReactNode; /** * Choose `default` or alternative `condensed` display styles */ @@ -55,10 +64,7 @@ export type EuiTabsProps = CommonProps & export type EuiTabRef = HTMLDivElement; -export const EuiTabs = React.forwardRef< - EuiTabRef, - PropsWithChildren ->( +export const EuiTabs = forwardRef>( ( { children, diff --git a/src/components/title/title.tsx b/src/components/title/title.tsx index 49d2d00244a7..8e908dfff1f1 100644 --- a/src/components/title/title.tsx +++ b/src/components/title/title.tsx @@ -41,6 +41,9 @@ export const TEXT_TRANSFORM = keysOf(textTransformToClassNameMap); export type EuiTitleTextTransform = keyof typeof textTransformToClassNameMap; export type EuiTitleProps = CommonProps & { + /** + * ReactElement to render as this component's content + */ children: ReactElement; size?: EuiTitleSize; textTransform?: EuiTitleTextTransform; diff --git a/src/components/toast/global_toast_list_item.tsx b/src/components/toast/global_toast_list_item.tsx index ec06f5526807..9a8562386420 100644 --- a/src/components/toast/global_toast_list_item.tsx +++ b/src/components/toast/global_toast_list_item.tsx @@ -23,6 +23,9 @@ import { CommonProps } from '../common'; export interface EuiGlobalToastListItemProps { isDismissed?: boolean; + /** + * ReactElement to render as this component's content + */ children?: ReactElement; } diff --git a/src/components/toggle/toggle.tsx b/src/components/toggle/toggle.tsx index fc80923702ed..e6d4bc627133 100644 --- a/src/components/toggle/toggle.tsx +++ b/src/components/toggle/toggle.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { ChangeEventHandler, HTMLAttributes } from 'react'; +import React, { ChangeEventHandler, HTMLAttributes, SFC } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../common'; @@ -58,7 +58,7 @@ export type EuiToggleProps = HTMLAttributes & value?: string | number; }; -export const EuiToggle: React.SFC = ({ +export const EuiToggle: SFC = ({ id, className, checked, diff --git a/src/components/token/__snapshots__/token.test.tsx.snap b/src/components/token/__snapshots__/token.test.tsx.snap index 1004a14d3d01..764adb2b25cb 100644 --- a/src/components/token/__snapshots__/token.test.tsx.snap +++ b/src/components/token/__snapshots__/token.test.tsx.snap @@ -163,6 +163,16 @@ exports[`EuiToken props fill none is rendered 1`] = ` `; +exports[`EuiToken props iconType as EuiTokenMapType __docgenInfo is rendered 1`] = ` + +
+ +`; + exports[`EuiToken props iconType as EuiTokenMapType tokenAlias is rendered 1`] = ` ; + Omit, 'title'>; export const EuiToken: FunctionComponent = ({ iconType, diff --git a/src/components/tool_tip/tool_tip.tsx b/src/components/tool_tip/tool_tip.tsx index d80dc04db9f4..b6f8a3f5f5d3 100644 --- a/src/components/tool_tip/tool_tip.tsx +++ b/src/components/tool_tip/tool_tip.tsx @@ -80,7 +80,7 @@ export interface Props { /** * The in-view trigger for your tooltip. */ - children: ReactElement; + children: ReactElement; /** * Passes onto the tooltip itself, not the trigger. */ diff --git a/yarn.lock b/yarn.lock index 5f8007d1305a..c4b5e85abb44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,7 +48,7 @@ invariant "^2.2.4" semver "^5.5.0" -"@babel/core@^7.0.0", "@babel/core@^7.1.0": +"@babel/core@^7.1.0": version "7.7.7" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.7.tgz#ee155d2e12300bcc0cff6a8ad46f2af5063803e9" integrity sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ== @@ -2559,16 +2559,6 @@ ast-types-flow@0.0.7, ast-types-flow@^0.0.7: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= -ast-types@0.11.3: - version "0.11.3" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8" - integrity sha512-XA5o5dsNw8MhyW0Q7MWXJWc4oOzZKbdsEJq45h7c8q/d9DwWZ5F2ugUc1PuMLPGsUnphCt/cNDHu8JeBbxf1qA== - -ast-types@0.12.4: - version "0.12.4" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.12.4.tgz#71ce6383800f24efc9a1a3308f3a6e420a0974d1" - integrity sha512-ky/YVYCbtVAS8TdMIaTiPFHwEpRB5z1hctepJplTr3UW5q8TDrpIMCILyk8pmLxGtn2KCtC/lSn7zOsaI7nzDw== - astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -2811,15 +2801,6 @@ babel-plugin-pegjs-inline-precompile@^0.1.0: babylon "^6.18.0" pegjs "^0.10.0" -babel-plugin-react-docgen@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-react-docgen/-/babel-plugin-react-docgen-3.1.0.tgz#14b02b363a38cc9e08c871df16960d27ef92030f" - integrity sha512-W6xqZnZIWjZuE9IjP7XolxxgFGB5Y9GZk4cLPSWKa10MrT86q7bX4ke9jbrNhFVIRhbmzL8wE1Sn++mIWoJLbw== - dependencies: - lodash "^4.17.11" - react-docgen "^4.1.0" - recast "^0.14.7" - babel-preset-jest@^24.1.0: version "24.1.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.1.0.tgz#83bc564fdcd4903641af65ec63f2f5de6b04132e" @@ -6370,7 +6351,7 @@ esprima@^2.6.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= -esprima@^4.0.0, esprima@~4.0.0: +esprima@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" integrity sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw== @@ -11658,13 +11639,6 @@ node-addon-api@^1.7.1: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== -node-dir@^0.1.10: - version "0.1.17" - resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" - integrity sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU= - dependencies: - minimatch "^3.0.2" - node-emoji@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" @@ -13865,7 +13839,7 @@ prism-react-renderer@^1.0.2: resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz#1c1be61b1eb9446a146ca7a50b7bcf36f2a70a44" integrity sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug== -private@^0.1.6, private@^0.1.8, private@~0.1.5: +private@^0.1.6: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== @@ -14285,18 +14259,10 @@ react-clientside-effect@^1.2.2: dependencies: "@babel/runtime" "^7.0.0" -react-docgen@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-4.1.1.tgz#8fef0212dbf14733e09edecef1de6b224d87219e" - integrity sha512-o1wdswIxbgJRI4pckskE7qumiFyqkbvCO++TylEDOo2RbMiueIOg8YzKU4X9++r0DjrbXePw/LHnh81GRBTWRw== - dependencies: - "@babel/core" "^7.0.0" - "@babel/runtime" "^7.0.0" - async "^2.1.4" - commander "^2.19.0" - doctrine "^3.0.0" - node-dir "^0.1.10" - recast "^0.17.3" +react-docgen-typescript@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-1.17.1.tgz#6df6a5bf9b340e45ed3f1590515013ba71d3078b" + integrity sha512-JahR6AvNOQ2+HC+jIzMuFw6VctUnComz84W5AlRVF53wOq2yRR0xosQ3NShjU7mC27McgfzoFKKzL5UBN86FXw== react-dom@^16.12.0: version "16.12.0" @@ -14734,26 +14700,6 @@ realpath-native@^1.0.2: dependencies: util.promisify "^1.0.0" -recast@^0.14.7: - version "0.14.7" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d" - integrity sha512-/nwm9pkrcWagN40JeJhkPaRxiHXBRkXyRh/hgU088Z/v+qCy+zIHHY6bC6o7NaKAxPqtE6nD8zBH1LfU0/Wx6A== - dependencies: - ast-types "0.11.3" - esprima "~4.0.0" - private "~0.1.5" - source-map "~0.6.1" - -recast@^0.17.3: - version "0.17.6" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.17.6.tgz#64ae98d0d2dfb10ff92ff5fb9ffb7371823b69fa" - integrity sha512-yoQRMRrK1lszNtbkGyM4kN45AwylV5hMiuEveUBlxytUViWevjvX6w+tzJt1LH4cfUhWt4NZvy3ThIhu6+m5wQ== - dependencies: - ast-types "0.12.4" - esprima "~4.0.0" - private "^0.1.8" - source-map "~0.6.1" - rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"