diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b44a60f2e3..ceb0dd26c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [`master`](https://github.com/elastic/eui/tree/master) - Fixed `EuiSuperDatePicker` to update `asyncInterval.isStopped` on a `isPaused` prop change. ([#2250](https://github.com/elastic/eui/pull/2250)) +- Converted table, popover, buttons, pagination, outside click detector, focus trap, context menu, and panel to TypeScript ([#2212](https://github.com/elastic/eui/pull/2212)) ## [`13.5.0`](https://github.com/elastic/eui/tree/v13.5.0) diff --git a/package.json b/package.json index f4602d36a38..cacbc48843c 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@types/react-is": "~16.3.0", "@types/react-virtualized": "^9.18.6", "@types/resize-observer-browser": "^0.1.1", + "@types/tabbable": "^3.1.0", "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^1.9.0", "@typescript-eslint/parser": "^1.9.0", diff --git a/scripts/babel/proptypes-from-ts-props/index.js b/scripts/babel/proptypes-from-ts-props/index.js index eaf2d1d53f3..99c0c9dfd32 100644 --- a/scripts/babel/proptypes-from-ts-props/index.js +++ b/scripts/babel/proptypes-from-ts-props/index.js @@ -5,6 +5,8 @@ const path = require('path'); const babelTemplate = require('babel-template'); const babelCore = require('@babel/core'); +const importedDefinitionsCache = new Map(); + // react-docgen does not understand typescript annotations function stripTypeScript(filename, ast) { return babelCore.transform( @@ -809,6 +811,16 @@ const typeDefinitionExtractors = { return []; } + if (importedDefinitionsCache.has(resolvedPath)) { + return importedDefinitionsCache.get(resolvedPath); + } + + // to support circular dependencies, create & pre-cache the array of imported dependencies + // this array is directly mutated after parsing the subsequent files, supporting + // the circular nature as values settle into the correct locations + const importedDefinitions = []; + importedDefinitionsCache.set(resolvedPath, importedDefinitions); + // load & parse the imported file const ast = parse(fs.readFileSync(resolvedPath).toString()); @@ -840,18 +852,15 @@ const typeDefinitionExtractors = { ); // for each importedTypeName, fully resolve the type information - const importedDefinitions = definitions.reduce( - (importedDefinitions, { name, definition }) => { + definitions.forEach( + ({ name, definition }) => { if (importedTypeNames.includes(name)) { // this type declaration is imported by the parent script const propTypes = getPropTypesForNode(definition, true, state); propTypes.isAlreadyResolved = true; // when getPropTypesForNode is called on this node later, tell it to skip processing importedDefinitions.push({ name, definition: propTypes }); } - - return importedDefinitions; - }, - [] + } ); // reset typeDefinitions and continue processing the original file @@ -1026,7 +1035,7 @@ function processComponentDeclaration(typeDefinition, path, state) { // import PropTypes library if it isn't already const proptypesBinding = getVariableBinding(path, 'PropTypes'); - if (proptypesBinding == null) { + if (proptypesBinding == null && state.get('hasInjectedPropTypes') !== true) { let targetNode; // find the first statement in the program and import PropTypes there targetNode = path; @@ -1043,6 +1052,7 @@ function processComponentDeclaration(typeDefinition, path, state) { types.stringLiteral('prop-types') ) ); + state.set('hasInjectedPropTypes', true); } } @@ -1268,3 +1278,7 @@ module.exports = function propTypesFromTypeScript({ types }) { }, }; }; + +module.exports.clearImportCache = function clearImportCache() { + importedDefinitionsCache.clear(); +} diff --git a/scripts/babel/proptypes-from-ts-props/index.test.js b/scripts/babel/proptypes-from-ts-props/index.test.js index 7edfa6061d8..e204af325bc 100644 --- a/scripts/babel/proptypes-from-ts-props/index.test.js +++ b/scripts/babel/proptypes-from-ts-props/index.test.js @@ -10,6 +10,9 @@ const babelOptions = { ], filename: 'somefile.tsx', }; +const babelPlugin = require('./index'); + +beforeEach(() => babelPlugin.clearImportCache()); describe('proptypes-from-ts-props', () => { diff --git a/src-docs/src/i18ntokens.json b/src-docs/src/i18ntokens.json index bb50ffa9ce5..f5cc28ea59b 100644 --- a/src-docs/src/i18ntokens.json +++ b/src-docs/src/i18ntokens.json @@ -421,11 +421,11 @@ "highlighting": "string", "loc": { "start": { - "line": 329, + "line": 337, "column": 14 }, "end": { - "line": 332, + "line": 340, "column": 16 } }, @@ -661,15 +661,15 @@ "highlighting": "string", "loc": { "start": { - "line": 37, + "line": 61, "column": 6 }, "end": { - "line": 41, + "line": 65, "column": 57 } }, - "filepath": "src/components/pagination/pagination.js" + "filepath": "src/components/pagination/pagination.tsx" }, { "token": "euiPagination.previousPage", @@ -677,15 +677,15 @@ "highlighting": "string", "loc": { "start": { - "line": 57, + "line": 81, "column": 4 }, "end": { - "line": 57, + "line": 81, "column": 72 } }, - "filepath": "src/components/pagination/pagination.js" + "filepath": "src/components/pagination/pagination.tsx" }, { "token": "euiPagination.pageOfTotal", @@ -693,15 +693,15 @@ "highlighting": "string", "loc": { "start": { - "line": 75, + "line": 99, "column": 6 }, "end": { - "line": 79, + "line": 103, "column": 53 } }, - "filepath": "src/components/pagination/pagination.js" + "filepath": "src/components/pagination/pagination.tsx" }, { "token": "euiPagination.jumpToLastPage", @@ -709,15 +709,15 @@ "highlighting": "string", "loc": { "start": { - "line": 120, + "line": 144, "column": 6 }, "end": { - "line": 124, + "line": 148, "column": 31 } }, - "filepath": "src/components/pagination/pagination.js" + "filepath": "src/components/pagination/pagination.tsx" }, { "token": "euiPagination.nextPage", @@ -725,15 +725,15 @@ "highlighting": "string", "loc": { "start": { - "line": 138, + "line": 162, "column": 4 }, "end": { - "line": 138, + "line": 162, "column": 64 } }, - "filepath": "src/components/pagination/pagination.js" + "filepath": "src/components/pagination/pagination.tsx" }, { "token": "euiPopover.screenReaderAnnouncement", @@ -741,15 +741,15 @@ "highlighting": "string", "loc": { "start": { - "line": 442, + "line": 589, "column": 14 }, "end": { - "line": 445, + "line": 592, "column": 16 } }, - "filepath": "src/components/popover/popover.js" + "filepath": "src/components/popover/popover.tsx" }, { "token": "euiSelectable.loadingOptions", @@ -917,15 +917,15 @@ "highlighting": "string", "loc": { "start": { - "line": 50, + "line": 49, "column": 8 }, "end": { - "line": 50, + "line": 49, "column": 72 } }, - "filepath": "src/components/table/mobile/table_sort_mobile.js" + "filepath": "src/components/table/mobile/table_sort_mobile.tsx" }, { "token": "euiTablePagination.rowsPerPage", @@ -933,15 +933,15 @@ "highlighting": "string", "loc": { "start": { - "line": 50, + "line": 62, "column": 8 }, "end": { - "line": 53, + "line": 65, "column": 10 } }, - "filepath": "src/components/table/table_pagination/table_pagination.js" + "filepath": "src/components/table/table_pagination/table_pagination.tsx" }, { "token": "euiTablePagination.rowsPerPageOption", @@ -949,15 +949,15 @@ "highlighting": "string", "loc": { "start": { - "line": 66, + "line": 78, "column": 8 }, "end": { - "line": 70, + "line": 82, "column": 10 } }, - "filepath": "src/components/table/table_pagination/table_pagination.js" + "filepath": "src/components/table/table_pagination/table_pagination.tsx" }, { "token": "euiToast.dismissToast", diff --git a/src/components/basic_table/__snapshots__/basic_table.test.js.snap b/src/components/basic_table/__snapshots__/basic_table.test.js.snap index 0fb11ef045d..054b85bca2e 100644 --- a/src/components/basic_table/__snapshots__/basic_table.test.js.snap +++ b/src/components/basic_table/__snapshots__/basic_table.test.js.snap @@ -48,12 +48,6 @@ exports[`EuiBasicTable cellProps renders cells with custom props from a callback align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -173,12 +167,6 @@ exports[`EuiBasicTable cellProps renders rows with custom props from an object 1 align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -300,12 +288,6 @@ exports[`EuiBasicTable empty is rendered 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -316,12 +298,6 @@ exports[`EuiBasicTable empty is rendered 1`] = ` align="center" colSpan={1} isMobileFullWidth={true} - mobileOptions={ - Object { - "show": true, - } - } - textOnly={true} > No items found @@ -380,12 +356,6 @@ exports[`EuiBasicTable empty renders a node as a custom message 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -396,12 +366,6 @@ exports[`EuiBasicTable empty renders a node as a custom message 1`] = ` align="center" colSpan={1} isMobileFullWidth={true} - mobileOptions={ - Object { - "show": true, - } - } - textOnly={true} >

no items, click @@ -468,12 +432,6 @@ exports[`EuiBasicTable empty renders a string as a custom message 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -484,12 +442,6 @@ exports[`EuiBasicTable empty renders a string as a custom message 1`] = ` align="center" colSpan={1} isMobileFullWidth={true} - mobileOptions={ - Object { - "show": true, - } - } - textOnly={true} > where my items at? @@ -548,12 +500,6 @@ exports[`EuiBasicTable footers do not render without a column footer definition align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -561,12 +507,6 @@ exports[`EuiBasicTable footers do not render without a column footer definition align="left" data-test-subj="tableHeaderCell_id_1" key="_data_h_id_1" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > ID @@ -574,12 +514,6 @@ exports[`EuiBasicTable footers do not render without a column footer definition align="left" data-test-subj="tableHeaderCell_age_2" key="_data_h_age_2" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Age @@ -802,13 +736,7 @@ exports[`EuiBasicTable footers render with pagination, selection, sorting, and f isSortAscending={true} isSorted={true} key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Name @@ -816,12 +744,6 @@ exports[`EuiBasicTable footers render with pagination, selection, sorting, and f align="left" data-test-subj="tableHeaderCell_id_1" key="_data_h_id_1" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > ID @@ -829,12 +751,6 @@ exports[`EuiBasicTable footers render with pagination, selection, sorting, and f align="left" data-test-subj="tableHeaderCell_age_2" key="_data_h_age_2" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Age @@ -1005,11 +921,9 @@ exports[`EuiBasicTable footers render with pagination, selection, sorting, and f @@ -1017,13 +931,11 @@ exports[`EuiBasicTable footers render with pagination, selection, sorting, and f ID @@ -1099,12 +1011,6 @@ exports[`EuiBasicTable itemIdToExpandedRowMap renders an expanded row 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -1133,14 +1039,7 @@ exports[`EuiBasicTable itemIdToExpandedRowMap renders an expanded row 1`] = ` isExpandedRow={true} >

Expanded row @@ -1235,12 +1134,6 @@ exports[`EuiBasicTable rowProps renders rows with custom props from a callback 1 align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -1366,12 +1259,6 @@ exports[`EuiBasicTable rowProps renders rows with custom props from an object 1` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -1497,12 +1384,6 @@ exports[`EuiBasicTable with pagination - 2nd page 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -1607,12 +1488,6 @@ exports[`EuiBasicTable with pagination 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -1734,12 +1609,6 @@ exports[`EuiBasicTable with pagination and error 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -1750,12 +1619,6 @@ exports[`EuiBasicTable with pagination and error 1`] = ` align="center" colSpan={1} isMobileFullWidth={true} - mobileOptions={ - Object { - "show": true, - } - } - textOnly={true} > Name @@ -1997,12 +1854,6 @@ exports[`EuiBasicTable with pagination, hiding the per page options 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -2159,13 +2010,7 @@ exports[`EuiBasicTable with pagination, selection and sorting 1`] = ` isSortAscending={true} isSorted={true} key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Name @@ -2354,25 +2199,13 @@ exports[`EuiBasicTable with pagination, selection, sorting and a single record a isSortAscending={true} isSorted={true} key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Name Actions @@ -2410,11 +2243,6 @@ exports[`EuiBasicTable with pagination, selection, sorting and a single record a align="right" hasActions={true} key="record_actions_1_1" - mobileOptions={ - Object { - "show": true, - } - } showOnHover={true} textOnly={false} > @@ -2472,11 +2300,6 @@ exports[`EuiBasicTable with pagination, selection, sorting and a single record a align="right" hasActions={true} key="record_actions_2_1" - mobileOptions={ - Object { - "show": true, - } - } showOnHover={true} textOnly={false} > @@ -2534,11 +2357,6 @@ exports[`EuiBasicTable with pagination, selection, sorting and a single record a align="right" hasActions={true} key="record_actions_3_1" - mobileOptions={ - Object { - "show": true, - } - } showOnHover={true} textOnly={false} > @@ -2663,13 +2481,7 @@ exports[`EuiBasicTable with pagination, selection, sorting and column dataType 1 isSortAscending={true} isSorted={true} key="_data_h_count_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Count @@ -2858,13 +2670,7 @@ exports[`EuiBasicTable with pagination, selection, sorting and column renderer 1 isSortAscending={true} isSorted={true} key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Name @@ -3053,25 +2859,13 @@ exports[`EuiBasicTable with pagination, selection, sorting and multiple record a isSortAscending={true} isSorted={true} key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Name Actions @@ -3109,11 +2903,6 @@ exports[`EuiBasicTable with pagination, selection, sorting and multiple record a align="right" hasActions={true} key="record_actions_1_1" - mobileOptions={ - Object { - "show": true, - } - } showOnHover={true} textOnly={false} > @@ -3177,11 +2966,6 @@ exports[`EuiBasicTable with pagination, selection, sorting and multiple record a align="right" hasActions={true} key="record_actions_2_1" - mobileOptions={ - Object { - "show": true, - } - } showOnHover={true} textOnly={false} > @@ -3245,11 +3029,6 @@ exports[`EuiBasicTable with pagination, selection, sorting and multiple record a align="right" hasActions={true} key="record_actions_3_1" - mobileOptions={ - Object { - "show": true, - } - } showOnHover={true} textOnly={false} > @@ -3380,13 +3159,7 @@ exports[`EuiBasicTable with pagination, selection, sorting, column renderer and isSortAscending={true} isSorted={true} key="_data_h_count_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Count @@ -3541,12 +3314,6 @@ exports[`EuiBasicTable with sortable columns and sorting disabled 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > Name @@ -3673,13 +3440,7 @@ exports[`EuiBasicTable with sorting 1`] = ` isSortAscending={true} isSorted={true} key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } onSort={[Function]} - scope="col" > Name diff --git a/src/components/basic_table/__snapshots__/default_item_action.test.js.snap b/src/components/basic_table/__snapshots__/default_item_action.test.js.snap index e07983baee7..ba136011d28 100644 --- a/src/components/basic_table/__snapshots__/default_item_action.test.js.snap +++ b/src/components/basic_table/__snapshots__/default_item_action.test.js.snap @@ -9,11 +9,9 @@ exports[`DefaultItemAction render - button 1`] = ` action1 @@ -29,11 +27,9 @@ exports[`DefaultItemAction render - icon 1`] = ` `; diff --git a/src/components/basic_table/__snapshots__/in_memory_table.test.js.snap b/src/components/basic_table/__snapshots__/in_memory_table.test.js.snap index afecec86f3c..fd3b2b209d8 100644 --- a/src/components/basic_table/__snapshots__/in_memory_table.test.js.snap +++ b/src/components/basic_table/__snapshots__/in_memory_table.test.js.snap @@ -150,12 +150,6 @@ exports[`EuiInMemoryTable behavior pagination 1`] = ` align="left" data-test-subj="tableHeaderCell_name_0" key="_data_h_name_0" - mobileOptions={ - Object { - "show": true, - } - } - scope="col" > - ); - } -}; - -EuiButton.propTypes = { - children: PropTypes.node, - className: PropTypes.string, - - /** - * See EuiIcon - */ - iconType: IconPropType, - iconSide: PropTypes.oneOf(ICON_SIDES), - - /** - * Add more focus to an action - */ - fill: PropTypes.bool, - - /** - * Define the color of the button - */ - color: PropTypes.oneOf(COLORS), - size: PropTypes.oneOf(SIZES), - - /** - * Expands button to fill the width of the parent - */ - fullWidth: PropTypes.bool, - isDisabled: PropTypes.bool, - href: PropTypes.string, - target: PropTypes.string, - rel: PropTypes.string, - onClick: PropTypes.func, - - /** - * Adds/swaps for loading spinner & disables - */ - isLoading: PropTypes.bool, - - /** - * Standard HTML attribute - */ - type: PropTypes.string, - buttonRef: PropTypes.func, - - /** - * Passes props to `euiButton__content` span - */ - contentProps: PropTypes.object, - - /** - * Passes props to `euiButton__text` span - */ - textProps: PropTypes.object, -}; - -EuiButton.defaultProps = { - size: 'm', - type: 'button', - iconSide: 'left', - color: 'primary', - fill: false, -}; diff --git a/src/components/button/button.test.js b/src/components/button/button.test.tsx similarity index 100% rename from src/components/button/button.test.js rename to src/components/button/button.test.tsx diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx new file mode 100644 index 00000000000..3fac06d3d08 --- /dev/null +++ b/src/components/button/button.tsx @@ -0,0 +1,184 @@ +import React, { + AnchorHTMLAttributes, + ButtonHTMLAttributes, + FunctionComponent, + HTMLAttributes, + MouseEventHandler, + Ref, +} from 'react'; +import classNames from 'classnames'; + +import { CommonProps, ExclusiveUnion, keysOf } from '../common'; +import { EuiLoadingSpinner } from '../loading'; + +import { getSecureRelForTarget } from '../../services'; + +import { IconType, EuiIcon } from '../icon'; + +export type ButtonIconSide = 'left' | 'right'; + +export type ButtonColor = + | 'primary' + | 'secondary' + | 'warning' + | 'danger' + | 'ghost' + | 'text'; + +export type ButtonSize = 's' | 'm'; + +const colorToClassNameMap: { [color in ButtonColor]: string } = { + primary: 'euiButton--primary', + secondary: 'euiButton--secondary', + warning: 'euiButton--warning', + danger: 'euiButton--danger', + ghost: 'euiButton--ghost', + text: 'euiButton--text', +}; + +export const COLORS = keysOf(colorToClassNameMap); + +const sizeToClassNameMap: { [size in ButtonSize]: string | null } = { + s: 'euiButton--small', + m: null, +}; + +export const SIZES = keysOf(sizeToClassNameMap); + +const iconSideToClassNameMap: { [side in ButtonIconSide]: string | null } = { + left: null, + right: 'euiButton--iconRight', +}; + +export const ICON_SIDES = keysOf(iconSideToClassNameMap); + +export interface EuiButtonProps extends CommonProps { + iconType?: IconType; + iconSide?: ButtonIconSide; + fill?: boolean; + color?: ButtonColor; + size?: ButtonSize; + isLoading?: boolean; + isDisabled?: boolean; + fullWidth?: boolean; + contentProps?: HTMLAttributes; + textProps?: HTMLAttributes; +} + +type EuiButtonPropsForAnchor = EuiButtonProps & + AnchorHTMLAttributes & { + href?: string; + onClick?: MouseEventHandler; + buttonRef?: Ref; + }; + +type EuiButtonPropsForButton = EuiButtonProps & + ButtonHTMLAttributes & { + onClick?: MouseEventHandler; + buttonRef?: Ref; + }; + +export type Props = ExclusiveUnion< + EuiButtonPropsForAnchor, + EuiButtonPropsForButton +>; + +export const EuiButton: FunctionComponent = ({ + children, + className, + iconType, + iconSide = 'left', + color = 'primary', + size = 'm', + fill = false, + isDisabled, + isLoading, + href, + target, + rel, + type = 'button', + buttonRef, + contentProps, + textProps, + fullWidth, + ...rest +}) => { + // If in the loading state, force disabled to true + isDisabled = isLoading ? true : isDisabled; + + const classes = classNames( + 'euiButton', + color ? colorToClassNameMap[color] : null, + size ? sizeToClassNameMap[size] : null, + iconSide ? iconSideToClassNameMap[iconSide] : null, + className, + { + 'euiButton--fill': fill, + 'euiButton--fullWidth': fullWidth, + } + ); + + const contentClassNames = classNames( + 'euiButton__content', + contentProps && contentProps.className + ); + + const textClassNames = classNames( + 'euiButton__text', + textProps && textProps.className + ); + + // Add an icon to the button if one exists. + let buttonIcon; + + if (isLoading) { + buttonIcon = ; + } else if (iconType) { + buttonIcon = ( +