diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7a69cf3e3..e867ac9c646 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -No public interface changes since `5.5.1`. +- Convert `EuiIcon` to TypeScript ([#1355](https://github.com/elastic/eui/pull/1355)) ## [`5.5.1`](https://github.com/elastic/eui/tree/v5.5.1) diff --git a/package.json b/package.json index 331fa0eccb6..c028c75ab66 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint-es-fix": "eslint --fix --cache --ignore-pattern \"**/*.snap.js\" \"src/**/*.js\" \"src-docs/**/*.js\"", "lint-sass": "sass-lint -v --max-warnings 0", "lint-sass-fix": "sass-lint-auto-fix -c ./.sass-lint-fix.yml", - "lint-ts": "tslint -c ./tslint.yaml -p ./tsconfig.json && tsc -p ./tsconfig.json --noEmit", + "lint-ts": "tsc -p ./tsconfig.json --noEmit && tslint -c ./tslint.yaml -p ./tsconfig.json", "lint-ts-fix": "tslint -c ./tslint.yaml -p ./tsconfig.json --fix", "lint-framer": "tslint -c ./tslint.yaml -p ./src-framer/tsconfig.json", "lint-framer-fix": "tslint -c ./tslint.yaml -p ./src-framer/tsconfig.json --fix", diff --git a/src/components/badge/index.d.ts b/src/components/badge/index.d.ts index 7f47aa83c4a..fad1e03fab8 100644 --- a/src/components/badge/index.d.ts +++ b/src/components/badge/index.d.ts @@ -1,4 +1,4 @@ -/// +import { IconType } from '../icon' /// import { HTMLAttributes, MouseEventHandler, SFC, ReactNode } from 'react'; diff --git a/src/components/button/index.d.ts b/src/components/button/index.d.ts index d17bf8f0454..36a6879e35e 100644 --- a/src/components/button/index.d.ts +++ b/src/components/button/index.d.ts @@ -1,5 +1,5 @@ import { CommonProps } from '../common'; -/// +import { IconType } from '../icon' import { SFC, ButtonHTMLAttributes, AnchorHTMLAttributes, MouseEventHandler, HTMLAttributes } from 'react'; diff --git a/src/components/call_out/index.d.ts b/src/components/call_out/index.d.ts index c9b51b64998..5cc151d8fc8 100644 --- a/src/components/call_out/index.d.ts +++ b/src/components/call_out/index.d.ts @@ -1,5 +1,5 @@ import { CommonProps, Omit } from '../common'; -/// +import { IconType } from '../icon' import { SFC, ReactNode, HTMLAttributes } from 'react'; diff --git a/src/components/common.ts b/src/components/common.ts index c552fa0d27f..2d6283530ab 100644 --- a/src/components/common.ts +++ b/src/components/common.ts @@ -13,3 +13,7 @@ export type RefCallback = ( // utility types: export type Omit = Pick>; + +export function keysOf(obj: T): K[] { + return Object.keys(obj) as K[]; +} diff --git a/src/components/empty_prompt/index.d.ts b/src/components/empty_prompt/index.d.ts index 34e05663894..076f001ac1f 100644 --- a/src/components/empty_prompt/index.d.ts +++ b/src/components/empty_prompt/index.d.ts @@ -1,5 +1,5 @@ import { CommonProps, Omit } from '../common'; -/// +import { IconColor, IconType } from '../icon' /// import { SFC, ReactNode, HTMLAttributes } from 'react'; diff --git a/src/components/form/field_number/index.d.ts b/src/components/form/field_number/index.d.ts index 4300b76c292..0f6f313cb2d 100644 --- a/src/components/form/field_number/index.d.ts +++ b/src/components/form/field_number/index.d.ts @@ -1,5 +1,5 @@ import { CommonProps } from '../../common'; -/// +import { IconType } from '../../icon' import { ReactNode, SFC, InputHTMLAttributes } from 'react'; diff --git a/src/components/health/index.d.ts b/src/components/health/index.d.ts index 0b078eb82be..c2a20d1fb4c 100644 --- a/src/components/health/index.d.ts +++ b/src/components/health/index.d.ts @@ -1,4 +1,4 @@ -/// +import { IconColor } from '../icon' import { SFC, HTMLAttributes } from 'react'; import { CommonProps } from '../common'; diff --git a/src/components/icon/__snapshots__/icon.test.js.snap b/src/components/icon/__snapshots__/icon.test.tsx.snap similarity index 99% rename from src/components/icon/__snapshots__/icon.test.js.snap rename to src/components/icon/__snapshots__/icon.test.tsx.snap index 06d6cdb6e62..9c6e1aabfcd 100644 --- a/src/components/icon/__snapshots__/icon.test.js.snap +++ b/src/components/icon/__snapshots__/icon.test.tsx.snap @@ -24,9 +24,10 @@ exports[`EuiIcon is rendered 1`] = ` `; -exports[`EuiIcon props size l is rendered 1`] = ` +exports[`EuiIcon props other props are passed through to the icon 1`] = ` `; -exports[`EuiIcon props size m is rendered 1`] = ` +exports[`EuiIcon props size l is rendered 1`] = ` `; -exports[`EuiIcon props size original is rendered 1`] = ` +exports[`EuiIcon props size m is rendered 1`] = ` `; -exports[`EuiIcon props size s is rendered 1`] = ` +exports[`EuiIcon props size original is rendered 1`] = ` `; -exports[`EuiIcon props size xl is rendered 1`] = ` +exports[`EuiIcon props size s is rendered 1`] = ` `; -exports[`EuiIcon props size xxl is rendered 1`] = ` +exports[`EuiIcon props size xl is rendered 1`] = ` `; -exports[`EuiIcon props tabIndex renders focusable="false" when -1 1`] = ` +exports[`EuiIcon props size xxl is rendered 1`] = ` `; -exports[`EuiIcon props tabIndex renders focusable="false" when not provided 1`] = ` +exports[`EuiIcon props tabIndex renders focusable="false" when -1 1`] = ` `; -exports[`EuiIcon props tabIndex renders focusable="true" when 0 1`] = ` +exports[`EuiIcon props tabIndex renders focusable="false" when not provided 1`] = ` `; -exports[`EuiIcon props title defaults to a humanized version of the type 1`] = ` - - - - - - -`; - -exports[`EuiIcon props title is rendered 1`] = ` +exports[`EuiIcon props tabIndex renders focusable="true" when 0 1`] = ` { }); describe('props', () => { - describe('title', () => { - test('defaults to a humanized version of the type', () => { + describe('other props', () => { + test('are passed through to the icon', () => { const component = render( - - ); - - expect(component).toMatchSnapshot(); - }); - - test('is rendered', () => { - const component = render( - + ); expect(component).toMatchSnapshot(); @@ -67,12 +59,12 @@ describe('EuiIcon', () => { }); test('renders focusable="false" when -1', () => { - const component = render(); + const component = render(); expect(component).toMatchSnapshot(); }); test('renders focusable="true" when 0', () => { - const component = render(); + const component = render(); expect(component).toMatchSnapshot(); }); }); diff --git a/src/components/icon/icon.js b/src/components/icon/icon.tsx similarity index 92% rename from src/components/icon/icon.js rename to src/components/icon/icon.tsx index 9226ff37413..d1011fd2877 100644 --- a/src/components/icon/icon.js +++ b/src/components/icon/icon.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import React, { SFC, SVGAttributes } from 'react'; import classNames from 'classnames'; +import { CommonProps, keysOf } from '../common'; + import addDataApp from './assets/app_add_data.svg'; import advancedSettingsApp from './assets/app_advanced_settings.svg'; import alert from './assets/alert.svg'; @@ -545,12 +546,14 @@ const typeToIconMap = { tokenEnumMember, tokenRepo, tokenSymbol, - tokenFile + tokenFile, }; -export const TYPES = Object.keys(typeToIconMap); +export const TYPES: IconType[] = keysOf(typeToIconMap); + +export type IconType = keyof typeof typeToIconMap; -const colorToClassMap = { +const colorToClassMap: { [color: string]: string | null; } = { default: null, primary: 'euiIcon--primary', secondary: 'euiIcon--secondary', @@ -563,7 +566,10 @@ const colorToClassMap = { ghost: 'euiIcon--ghost', }; -export const COLORS = Object.keys(colorToClassMap); +export const COLORS: IconColor[] = keysOf(colorToClassMap); + +// We accept arbitrary color strings, which are impossible to type. +export type IconColor = string | keyof typeof colorToClassMap; const sizeToClassNameMap = { original: null, @@ -574,11 +580,21 @@ const sizeToClassNameMap = { xxl: 'euiIcon--xxLarge', }; -export const SIZES = Object.keys(sizeToClassNameMap); +export const SIZES: IconSize[] = keysOf(sizeToClassNameMap); + +export type IconSize = keyof typeof sizeToClassNameMap; + +export interface EuiIconProps { + type?: IconType; + color?: IconColor; + size?: IconSize; +} + +type Props = CommonProps & SVGAttributes & EuiIconProps; -export const EuiIcon = ({ +export const EuiIcon: SFC = ({ type, - size, + size = 'm', color, className, tabIndex, @@ -587,14 +603,20 @@ export const EuiIcon = ({ let optionalColorClass = null; let optionalCustomStyles = null; - if (COLORS.indexOf(color) > -1) { - optionalColorClass = colorToClassMap[color]; + if (color) { + checkValidColor(color, type || 'empty'); + + if (COLORS.indexOf(color) > -1) { + optionalColorClass = colorToClassMap[color]; + } else { + optionalCustomStyles = { fill: color }; + } } else { - optionalCustomStyles = { fill: color }; + optionalCustomStyles = { fill: undefined }; } // These icons are a little special and get some extra CSS flexibility - const isAppIcon = /.+App$/.test(type) || /.+Job$/.test(type) || (type === 'dataVisualizer'); + const isAppIcon = type && (/.+App$/.test(type) || /.+Job$/.test(type) || (type === 'dataVisualizer')); const classes = classNames( 'euiIcon', @@ -603,10 +625,10 @@ export const EuiIcon = ({ { 'euiIcon--app': isAppIcon, }, - className, + className ); - const Svg = typeToIconMap[type] || empty; + const Svg = (type && typeToIconMap[type]) || empty; // This is a fix for IE and Edge, which ignores tabindex="-1" on an SVG, but respects // focusable="false". @@ -614,7 +636,7 @@ export const EuiIcon = ({ // which is how SVGs behave in Chrome, Safari, and FF. // - If tab index is -1, then the consumer wants the icon to not be focusable. // - For all other values, the consumer wants the icon to be focusable. - const focusable = (!tabIndex || tabIndex === '-1') ? 'false' : 'true'; + const focusable = (tabIndex == null || tabIndex === -1) ? 'false' : 'true'; return ( & EuiIconProps>; -} diff --git a/src/components/icon/index.js b/src/components/icon/index.ts similarity index 74% rename from src/components/icon/index.js rename to src/components/icon/index.ts index 5bcf80206e2..93fcbec7e9c 100644 --- a/src/components/icon/index.js +++ b/src/components/icon/index.ts @@ -1,5 +1,8 @@ export { EuiIcon, + IconColor, + IconSize, + IconType, TYPES as ICON_TYPES, SIZES as ICON_SIZES, COLORS as ICON_COLORS, diff --git a/src/components/index.d.ts b/src/components/index.d.ts index c21e0167e5d..7a5d0e37230 100644 --- a/src/components/index.d.ts +++ b/src/components/index.d.ts @@ -14,7 +14,6 @@ /// /// /// -/// /// /// /// diff --git a/src/components/key_pad_menu/index.d.ts b/src/components/key_pad_menu/index.d.ts index f5985a37d89..1edddc5aba6 100644 --- a/src/components/key_pad_menu/index.d.ts +++ b/src/components/key_pad_menu/index.d.ts @@ -1,4 +1,5 @@ import { CommonProps } from '../common'; +import { IconType } from '../icon'; import { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes, MouseEventHandler, ReactNode, SFC } from 'react'; diff --git a/src/components/table/index.d.ts b/src/components/table/index.d.ts index 05aa35f0a8a..c0885816a3d 100644 --- a/src/components/table/index.d.ts +++ b/src/components/table/index.d.ts @@ -1,6 +1,6 @@ /// import { CommonProps, NoArgCallback } from '../common'; -/// +import { IconType } from '../icon'; /// import { diff --git a/src/components/toast/index.d.ts b/src/components/toast/index.d.ts index 2dc7d6511a3..310a7e0f0e6 100644 --- a/src/components/toast/index.d.ts +++ b/src/components/toast/index.d.ts @@ -1,5 +1,5 @@ import { CommonProps } from '../common'; -/// +import { IconType } from '../icon'; import { Component, SFC, HTMLAttributes, ReactChild } from 'react'; diff --git a/src/components/token/index.d.ts b/src/components/token/index.d.ts index c2a8d6f98cf..9958ae9d376 100644 --- a/src/components/token/index.d.ts +++ b/src/components/token/index.d.ts @@ -1,7 +1,6 @@ -/// - import { SFC, HTMLAttributes } from 'react'; import { CommonProps } from '../common'; +import { IconType } from '../icon'; declare module '@elastic/eui' { diff --git a/src/custom_typings/index.d.ts b/src/custom_typings/index.d.ts new file mode 100644 index 00000000000..d0e39235e08 --- /dev/null +++ b/src/custom_typings/index.d.ts @@ -0,0 +1,9 @@ +declare module "*.png" { + const value: any; + export = value; +} + +declare module "*.svg" { + const value: any; + export = value; +} diff --git a/tsconfig.json b/tsconfig.json index ad523526e0c..8d973adad6d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -46,7 +46,11 @@ "esnext", // includes support for browser APIs "dom" - ] + ], + + // Specifies where to find library definitions. When this is explicitly set, + // it has to include the default location i.e. node_modules/@types + "typeRoots": ["node_modules/@types", "custom_typings"] }, "include": [ "./src/**/*",