diff --git a/package.json b/package.json index 47ed5e110b000..4c2ba4e3d0bb6 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "@kbn/logging": "link:bazel-bin/packages/kbn-logging", "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl", "@kbn/monaco": "link:bazel-bin/packages/kbn-monaco", + "@kbn/react-field": "link:bazel-bin/packages/kbn-react-field", "@kbn/rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils", "@kbn/securitysolution-autocomplete": "link:bazel-bin/packages/kbn-securitysolution-autocomplete", "@kbn/securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index ace4f982b8515..b52fb056380ee 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -36,6 +36,7 @@ filegroup( "//packages/kbn-optimizer:build", "//packages/kbn-plugin-generator:build", "//packages/kbn-plugin-helpers:build", + "//packages/kbn-react-field:build", "//packages/kbn-rule-data-utils:build", "//packages/kbn-securitysolution-autocomplete:build", "//packages/kbn-securitysolution-list-constants:build", diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 9a0863237fab1..6a76e23b7b3e9 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -28,6 +28,14 @@ const IS_CODE_COVERAGE = !!process.env.CODE_COVERAGE; const ISTANBUL_PRESET_PATH = require.resolve('@kbn/babel-preset/istanbul_preset'); const BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset'); +const nodeModulesButNotKbnPackages = (path: string) => { + if (!path.includes('node_modules')) { + return false; + } + + return !path.includes(`node_modules${Path.sep}@kbn${Path.sep}`); +}; + export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: WorkerConfig) { const ENTRY_CREATOR = require.resolve('./entry_point_creator'); @@ -134,7 +142,7 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: }, { test: /\.scss$/, - exclude: /node_modules/, + exclude: nodeModulesButNotKbnPackages, oneOf: [ ...worker.themeTags.map((theme) => ({ resourceQuery: `?${theme}`, diff --git a/packages/kbn-react-field/BUILD.bazel b/packages/kbn-react-field/BUILD.bazel new file mode 100644 index 0000000000000..15964ee748f5b --- /dev/null +++ b/packages/kbn-react-field/BUILD.bazel @@ -0,0 +1,119 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") + +PKG_BASE_NAME = "kbn-react-field" +PKG_REQUIRE_NAME = "@kbn/react-field" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.scss", + "src/**/*.svg", + ], + exclude = [ + "**/*.test.*", + "**/__fixtures__/**", + "**/__snapshots__/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md" +] + +RUNTIME_DEPS = [ + "@npm//prop-types", + "@npm//react", + "@npm//classnames", + "@npm//@elastic/eui", + "//packages/kbn-i18n", +] + +TYPES_DEPS = [ + "//packages/kbn-babel-preset", + "//packages/kbn-i18n", + "@npm//tslib", + "@npm//@types/jest", + "@npm//@types/prop-types", + "@npm//@types/classnames", + "@npm//@types/react", + "@npm//@elastic/eui", + "@npm//resize-observer-polyfill", +] + +jsts_transpiler( + name = "target_webpack", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, + additional_args = [ + "--copy-files", + "--quiet" + ], +) + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), + additional_args = [ + "--copy-files", + "--quiet" + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_webpack", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-react-field/README.md b/packages/kbn-react-field/README.md new file mode 100644 index 0000000000000..279c10b3aaecf --- /dev/null +++ b/packages/kbn-react-field/README.md @@ -0,0 +1 @@ +Sharable field type related React components diff --git a/packages/kbn-react-field/jest.config.js b/packages/kbn-react-field/jest.config.js new file mode 100644 index 0000000000000..1549cd0071356 --- /dev/null +++ b/packages/kbn-react-field/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['<rootDir>/packages/kbn-react-field'], +}; diff --git a/packages/kbn-react-field/package.json b/packages/kbn-react-field/package.json new file mode 100644 index 0000000000000..3cbfdfa010ba0 --- /dev/null +++ b/packages/kbn-react-field/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/react-field", + "main": "./target_node/index.js", + "browser": "./target_webpack/index.js", + "types": "./target_types/index.d.ts", + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0", + "private": true +} \ No newline at end of file diff --git a/packages/kbn-react-field/src/field_button/__snapshots__/field_button.test.tsx.snap b/packages/kbn-react-field/src/field_button/__snapshots__/field_button.test.tsx.snap new file mode 100644 index 0000000000000..e65b5fcb8fbbd --- /dev/null +++ b/packages/kbn-react-field/src/field_button/__snapshots__/field_button.test.tsx.snap @@ -0,0 +1,134 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`fieldAction is rendered 1`] = ` +<div + className="kbnFieldButton" +> + <button + className="kbn-resetFocusState kbnFieldButton__button" + onClick={[Function]} + > + <span + className="kbnFieldButton__name" + > + name + </span> + </button> + <div + className="kbnFieldButton__fieldAction" + > + <span> + fieldAction + </span> + </div> +</div> +`; + +exports[`fieldIcon is rendered 1`] = ` +<div + className="kbnFieldButton" +> + <button + className="kbn-resetFocusState kbnFieldButton__button" + onClick={[Function]} + > + <span + className="kbnFieldButton__fieldIcon" + > + <span> + fieldIcon + </span> + </span> + <span + className="kbnFieldButton__name" + > + name + </span> + </button> +</div> +`; + +exports[`isActive defaults to false 1`] = ` +<div + className="kbnFieldButton" +> + <button + className="kbn-resetFocusState kbnFieldButton__button" + onClick={[Function]} + > + <span + className="kbnFieldButton__name" + > + name + </span> + </button> +</div> +`; + +exports[`isActive renders true 1`] = ` +<div + className="kbnFieldButton kbnFieldButton-isActive" +> + <button + className="kbn-resetFocusState kbnFieldButton__button" + onClick={[Function]} + > + <span + className="kbnFieldButton__name" + > + name + </span> + </button> +</div> +`; + +exports[`isDraggable is rendered 1`] = ` +<div + className="kbnFieldButton kbnFieldButton--isDraggable" +> + <button + className="kbn-resetFocusState kbnFieldButton__button" + onClick={[Function]} + > + <span + className="kbnFieldButton__name" + > + name + </span> + </button> +</div> +`; + +exports[`sizes m is applied 1`] = ` +<div + className="kbnFieldButton" +> + <button + className="kbn-resetFocusState kbnFieldButton__button" + onClick={[Function]} + > + <span + className="kbnFieldButton__name" + > + name + </span> + </button> +</div> +`; + +exports[`sizes s is applied 1`] = ` +<div + className="kbnFieldButton kbnFieldButton--small" +> + <button + className="kbn-resetFocusState kbnFieldButton__button" + onClick={[Function]} + > + <span + className="kbnFieldButton__name" + > + name + </span> + </button> +</div> +`; diff --git a/packages/kbn-react-field/src/field_button/field_button.scss b/packages/kbn-react-field/src/field_button/field_button.scss new file mode 100644 index 0000000000000..f71e097ab7138 --- /dev/null +++ b/packages/kbn-react-field/src/field_button/field_button.scss @@ -0,0 +1,76 @@ +.kbnFieldButton { + @include euiFontSizeS; + border-radius: $euiBorderRadius; + margin-bottom: $euiSizeXS; + display: flex; + align-items: center; + transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance, + background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation + + &:focus-within, + &-isActive { + @include euiFocusRing; + } +} + +.kbnFieldButton--isDraggable { + background: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightestShade); + + &:hover, + &:focus, + &:focus-within { + @include euiBottomShadowMedium; + border-radius: $euiBorderRadius; + z-index: 2; + } + + .kbnFieldButton__button { + &:hover, + &:focus { + cursor: grab; + } + } +} + +.kbnFieldButton__button { + flex-grow: 1; + text-align: left; + padding: $euiSizeS; + display: flex; + align-items: flex-start; + line-height: normal; +} + +.kbnFieldButton__fieldIcon { + flex-shrink: 0; + line-height: 0; + margin-right: $euiSizeS; +} + +.kbnFieldButton__name { + flex-grow: 1; + word-break: break-word; +} + +.kbnFieldButton__infoIcon { + flex-shrink: 0; + margin-left: $euiSizeXS; +} + +.kbnFieldButton__fieldAction { + margin-right: $euiSizeS; +} + +// Reduce text size and spacing for the small size +.kbnFieldButton--small { + font-size: $euiFontSizeXS; + + .kbnFieldButton__button { + padding: $euiSizeXS; + } + + .kbnFieldButton__fieldIcon, + .kbnFieldButton__fieldAction { + margin-right: $euiSizeXS; + } +} diff --git a/packages/kbn-react-field/src/field_button/field_button.test.tsx b/packages/kbn-react-field/src/field_button/field_button.test.tsx new file mode 100644 index 0000000000000..e61b41187ba7e --- /dev/null +++ b/packages/kbn-react-field/src/field_button/field_button.test.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { FieldButton, SIZES } from './field_button'; + +const noop = () => {}; + +describe('sizes', () => { + SIZES.forEach((size) => { + test(`${size} is applied`, () => { + const component = shallow(<FieldButton onClick={noop} fieldName="name" size={size} />); + expect(component).toMatchSnapshot(); + }); + }); +}); + +describe('isDraggable', () => { + it('is rendered', () => { + const component = shallow(<FieldButton onClick={noop} fieldName="name" isDraggable />); + expect(component).toMatchSnapshot(); + }); +}); + +describe('fieldIcon', () => { + it('is rendered', () => { + const component = shallow( + <FieldButton onClick={noop} fieldName="name" fieldIcon={<span>fieldIcon</span>} /> + ); + expect(component).toMatchSnapshot(); + }); +}); + +describe('fieldAction', () => { + it('is rendered', () => { + const component = shallow( + <FieldButton onClick={noop} fieldName="name" fieldAction={<span>fieldAction</span>} /> + ); + expect(component).toMatchSnapshot(); + }); +}); + +describe('isActive', () => { + it('defaults to false', () => { + const component = shallow(<FieldButton onClick={noop} fieldName="name" />); + expect(component).toMatchSnapshot(); + }); + it('renders true', () => { + const component = shallow(<FieldButton onClick={noop} fieldName="name" isActive />); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/packages/kbn-react-field/src/field_button/field_button.tsx b/packages/kbn-react-field/src/field_button/field_button.tsx new file mode 100644 index 0000000000000..4871fea049710 --- /dev/null +++ b/packages/kbn-react-field/src/field_button/field_button.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import './field_button.scss'; +import classNames from 'classnames'; +import React, { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react'; +import { CommonProps } from '@elastic/eui'; + +export interface FieldButtonProps extends HTMLAttributes<HTMLDivElement> { + /** + * Label for the button + */ + fieldName: ReactNode; + /** + * Icon representing the field type. + * Recommend using FieldIcon + */ + fieldIcon?: ReactNode; + /** + * An optional node to place inside and at the end of the <button> + */ + fieldInfoIcon?: ReactNode; + /** + * An optional node to place outside of and to the right of the <button> + */ + fieldAction?: ReactNode; + /** + * Adds a forced focus ring to the whole component + */ + isActive?: boolean; + /** + * Styles the component differently to indicate it is draggable + */ + isDraggable?: boolean; + /** + * Use the small size in condensed areas + */ + size?: ButtonSize; + className?: string; + /** + * The component will render a `<button>` when provided an `onClick` + */ + onClick?: () => void; + /** + * Applies to the inner `<button>` or `<div>` + */ + dataTestSubj?: string; + /** + * Pass more button props to the `<button>` element + */ + buttonProps?: ButtonHTMLAttributes<HTMLButtonElement> & CommonProps; +} + +const sizeToClassNameMap = { + s: 'kbnFieldButton--small', + m: null, +} as const; + +export type ButtonSize = keyof typeof sizeToClassNameMap; + +export const SIZES = Object.keys(sizeToClassNameMap) as ButtonSize[]; + +export function FieldButton({ + size = 'm', + isActive = false, + fieldIcon, + fieldName, + fieldInfoIcon, + fieldAction, + className, + isDraggable = false, + onClick, + dataTestSubj, + buttonProps, + ...rest +}: FieldButtonProps) { + const classes = classNames( + 'kbnFieldButton', + size ? sizeToClassNameMap[size] : null, + { 'kbnFieldButton-isActive': isActive }, + { 'kbnFieldButton--isDraggable': isDraggable }, + className + ); + + const contentClasses = classNames('kbn-resetFocusState', 'kbnFieldButton__button'); + + const innerContent = ( + <> + {fieldIcon && <span className="kbnFieldButton__fieldIcon">{fieldIcon}</span>} + {fieldName && <span className="kbnFieldButton__name">{fieldName}</span>} + {fieldInfoIcon && <div className="kbnFieldButton__infoIcon">{fieldInfoIcon}</div>} + </> + ); + + return ( + <div className={classes} {...rest}> + {onClick ? ( + <button + onClick={(e) => { + if (e.type === 'click') { + e.currentTarget.focus(); + } + onClick(); + }} + data-test-subj={dataTestSubj} + className={contentClasses} + {...buttonProps} + > + {innerContent} + </button> + ) : ( + <div className={contentClasses} data-test-subj={dataTestSubj}> + {innerContent} + </div> + )} + + {fieldAction && <div className="kbnFieldButton__fieldAction">{fieldAction}</div>} + </div> + ); +} diff --git a/packages/kbn-react-field/src/field_button/index.ts b/packages/kbn-react-field/src/field_button/index.ts new file mode 100644 index 0000000000000..17c2321c00407 --- /dev/null +++ b/packages/kbn-react-field/src/field_button/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldButton, FieldButtonProps, ButtonSize } from './field_button'; diff --git a/packages/kbn-react-field/src/field_icon/__snapshots__/field_icon.test.tsx.snap b/packages/kbn-react-field/src/field_icon/__snapshots__/field_icon.test.tsx.snap new file mode 100644 index 0000000000000..f6870a5209c1e --- /dev/null +++ b/packages/kbn-react-field/src/field_icon/__snapshots__/field_icon.test.tsx.snap @@ -0,0 +1,190 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FieldIcon changes fill when scripted is true 1`] = ` +<EuiToken + aria-label="test" + className="kbnFieldIcon" + fill="dark" + iconType="tokenNumber" + size="s" + title="test" +/> +`; + +exports[`FieldIcon renders an icon for an unknown type 1`] = ` +<EuiToken + aria-label="test" + className="kbnFieldIcon" + color="gray" + iconType="questionInCircle" + size="s" + title="test" +/> +`; + +exports[`FieldIcon renders known field types _source is rendered 1`] = ` +<EuiToken + aria-label="_source" + className="kbnFieldIcon" + color="gray" + iconType="editorCodeBlock" + size="s" + title="_source" +/> +`; + +exports[`FieldIcon renders known field types boolean is rendered 1`] = ` +<EuiToken + aria-label="boolean" + className="kbnFieldIcon" + iconType="tokenBoolean" + size="s" + title="boolean" +/> +`; + +exports[`FieldIcon renders known field types conflict is rendered 1`] = ` +<EuiToken + aria-label="conflict" + className="kbnFieldIcon" + color="euiColorVis9" + iconType="alert" + shape="square" + size="s" + title="conflict" +/> +`; + +exports[`FieldIcon renders known field types date is rendered 1`] = ` +<EuiToken + aria-label="date" + className="kbnFieldIcon" + iconType="tokenDate" + size="s" + title="date" +/> +`; + +exports[`FieldIcon renders known field types date_range is rendered 1`] = ` +<EuiToken + aria-label="date_range" + className="kbnFieldIcon" + iconType="tokenDate" + size="s" + title="date_range" +/> +`; + +exports[`FieldIcon renders known field types geo_point is rendered 1`] = ` +<EuiToken + aria-label="geo_point" + className="kbnFieldIcon" + iconType="tokenGeo" + size="s" + title="geo_point" +/> +`; + +exports[`FieldIcon renders known field types geo_shape is rendered 1`] = ` +<EuiToken + aria-label="geo_shape" + className="kbnFieldIcon" + iconType="tokenGeo" + size="s" + title="geo_shape" +/> +`; + +exports[`FieldIcon renders known field types ip is rendered 1`] = ` +<EuiToken + aria-label="ip" + className="kbnFieldIcon" + iconType="tokenIP" + size="s" + title="ip" +/> +`; + +exports[`FieldIcon renders known field types ip_range is rendered 1`] = ` +<EuiToken + aria-label="ip_range" + className="kbnFieldIcon" + iconType="tokenIP" + size="s" + title="ip_range" +/> +`; + +exports[`FieldIcon renders known field types murmur3 is rendered 1`] = ` +<EuiToken + aria-label="murmur3" + className="kbnFieldIcon" + iconType="tokenFile" + size="s" + title="murmur3" +/> +`; + +exports[`FieldIcon renders known field types nested is rendered 1`] = ` +<EuiToken + aria-label="nested" + className="kbnFieldIcon" + iconType="tokenNested" + size="s" + title="nested" +/> +`; + +exports[`FieldIcon renders known field types number is rendered 1`] = ` +<EuiToken + aria-label="number" + className="kbnFieldIcon" + iconType="tokenNumber" + size="s" + title="number" +/> +`; + +exports[`FieldIcon renders known field types number_range is rendered 1`] = ` +<EuiToken + aria-label="number_range" + className="kbnFieldIcon" + iconType="tokenNumber" + size="s" + title="number_range" +/> +`; + +exports[`FieldIcon renders known field types string is rendered 1`] = ` +<EuiToken + aria-label="string" + className="kbnFieldIcon" + iconType="tokenString" + size="s" + title="string" +/> +`; + +exports[`FieldIcon renders with className if provided 1`] = ` +<EuiToken + aria-label="test" + className="kbnFieldIcon myClass" + color="gray" + iconType="questionInCircle" + size="s" + title="test" +/> +`; + +exports[`FieldIcon supports same props as EuiToken 1`] = ` +<EuiToken + aria-label="test" + className="kbnFieldIcon" + color="euiColorVis0" + fill="none" + iconType="tokenNumber" + shape="circle" + size="l" + title="test" +/> +`; diff --git a/packages/kbn-react-field/src/field_icon/field_icon.test.tsx b/packages/kbn-react-field/src/field_icon/field_icon.test.tsx new file mode 100644 index 0000000000000..cfc67592cd5ea --- /dev/null +++ b/packages/kbn-react-field/src/field_icon/field_icon.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { FieldIcon, typeToEuiIconMap } from './field_icon'; + +const availableTypes = Object.keys(typeToEuiIconMap); + +describe('FieldIcon renders known field types', () => { + availableTypes.forEach((type) => { + test(`${type} is rendered`, () => { + const component = shallow(<FieldIcon type={type} />); + expect(component).toMatchSnapshot(); + }); + }); +}); + +test('FieldIcon renders an icon for an unknown type', () => { + const component = shallow(<FieldIcon type="sdfsdf" label="test" />); + expect(component).toMatchSnapshot(); +}); + +test('FieldIcon supports same props as EuiToken', () => { + const component = shallow( + <FieldIcon + type="number" + label="test" + color="euiColorVis0" + size="l" + shape="circle" + fill="none" + /> + ); + expect(component).toMatchSnapshot(); +}); + +test('FieldIcon changes fill when scripted is true', () => { + const component = shallow(<FieldIcon type="number" label="test" scripted={true} />); + expect(component).toMatchSnapshot(); +}); + +test('FieldIcon renders with className if provided', () => { + const component = shallow(<FieldIcon type="sdfsdf" label="test" className="myClass" />); + expect(component).toMatchSnapshot(); +}); diff --git a/packages/kbn-react-field/src/field_icon/field_icon.tsx b/packages/kbn-react-field/src/field_icon/field_icon.tsx new file mode 100644 index 0000000000000..cd3c7ee9e7a99 --- /dev/null +++ b/packages/kbn-react-field/src/field_icon/field_icon.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import classNames from 'classnames'; +import { EuiToken, EuiTokenProps } from '@elastic/eui'; + +export interface FieldIconProps extends Omit<EuiTokenProps, 'iconType'> { + type: + | 'boolean' + | 'conflict' + | 'date' + | 'date_range' + | 'geo_point' + | 'geo_shape' + | 'ip' + | 'ip_range' + | 'murmur3' + | 'number' + | 'number_range' + | '_source' + | 'string' + | string + | 'nested'; + label?: string; + scripted?: boolean; +} + +// defaultIcon => a unknown datatype +const defaultIcon = { iconType: 'questionInCircle', color: 'gray' }; + +export const typeToEuiIconMap: Partial<Record<string, EuiTokenProps>> = { + boolean: { iconType: 'tokenBoolean' }, + // icon for an index pattern mapping conflict in discover + conflict: { iconType: 'alert', color: 'euiColorVis9', shape: 'square' }, + date: { iconType: 'tokenDate' }, + date_range: { iconType: 'tokenDate' }, + geo_point: { iconType: 'tokenGeo' }, + geo_shape: { iconType: 'tokenGeo' }, + ip: { iconType: 'tokenIP' }, + ip_range: { iconType: 'tokenIP' }, + // is a plugin's data type https://www.elastic.co/guide/en/elasticsearch/plugins/current/mapper-murmur3-usage.html + murmur3: { iconType: 'tokenFile' }, + number: { iconType: 'tokenNumber' }, + number_range: { iconType: 'tokenNumber' }, + _source: { iconType: 'editorCodeBlock', color: 'gray' }, + string: { iconType: 'tokenString' }, + nested: { iconType: 'tokenNested' }, +}; + +/** + * Field token icon used across the app + */ +export function FieldIcon({ + type, + label, + size = 's', + scripted, + className, + ...rest +}: FieldIconProps) { + const token = typeToEuiIconMap[type] || defaultIcon; + + return ( + <EuiToken + {...token} + className={classNames('kbnFieldIcon', className)} + aria-label={label || type} + title={label || type} + size={size as EuiTokenProps['size']} + fill={scripted ? 'dark' : undefined} + {...rest} + /> + ); +} diff --git a/packages/kbn-react-field/src/field_icon/index.ts b/packages/kbn-react-field/src/field_icon/index.ts new file mode 100644 index 0000000000000..431525fc8e428 --- /dev/null +++ b/packages/kbn-react-field/src/field_icon/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldIcon, FieldIconProps } from './field_icon'; diff --git a/packages/kbn-react-field/src/index.ts b/packages/kbn-react-field/src/index.ts new file mode 100644 index 0000000000000..af50b139a2918 --- /dev/null +++ b/packages/kbn-react-field/src/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { FieldIconProps, FieldIcon } from './field_icon'; +export { FieldButtonProps, ButtonSize, FieldButton } from './field_button'; diff --git a/packages/kbn-react-field/tsconfig.json b/packages/kbn-react-field/tsconfig.json new file mode 100644 index 0000000000000..90c8a63c746f1 --- /dev/null +++ b/packages/kbn-react-field/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "./target_types", + "sourceMap": true, + "sourceRoot": "../../../../../packages/kbn-react-field/src", + "types": [ + "jest", + "node", + "resize-observer-polyfill" + ] + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + ], + "exclude": [ + "**/__fixtures__/**/*" + ] +} diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx index 707514073e23e..71b74836ad4fc 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx @@ -23,8 +23,8 @@ import { import { i18n } from '@kbn/i18n'; import { UiCounterMetricType } from '@kbn/analytics'; import classNames from 'classnames'; +import { FieldIcon, FieldButton } from '@kbn/react-field'; import { DiscoverFieldDetails } from './discover_field_details'; -import { FieldIcon, FieldButton } from '../../../../../../../kibana_react/public'; import { FieldDetails } from './types'; import { IndexPatternField, IndexPattern } from '../../../../../../../data/public'; import { getFieldTypeName } from './lib/get_field_type_name'; diff --git a/src/plugins/discover/public/application/components/field_name/field_name.tsx b/src/plugins/discover/public/application/components/field_name/field_name.tsx index 0e8ca31f6379a..47400ebdf0f53 100644 --- a/src/plugins/discover/public/application/components/field_name/field_name.tsx +++ b/src/plugins/discover/public/application/components/field_name/field_name.tsx @@ -11,7 +11,7 @@ import './field_name.scss'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { FieldIcon, FieldIconProps } from '../../../../../kibana_react/public'; +import { FieldIcon, FieldIconProps } from '@kbn/react-field'; import { getFieldTypeName } from './field_type_name'; import { IndexPatternField } from '../../../../../data/public'; import { getFieldSubtypeMulti } from '../../../../../data/common'; diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 6fccb804c357f..30fffd57f5987 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -16,8 +16,6 @@ export * from './context'; export * from './overview_page'; export * from './overlays'; export * from './ui_settings'; -export * from './field_icon'; -export * from './field_button'; export * from './table_list_view'; export * from './toolbar_button'; export * from './split_panel'; diff --git a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js index dc516060ea360..7db6bf8948f8b 100644 --- a/x-pack/plugins/canvas/shareable_runtime/webpack.config.js +++ b/x-pack/plugins/canvas/shareable_runtime/webpack.config.js @@ -18,6 +18,14 @@ const { const isProd = process.env.NODE_ENV === 'production'; +const nodeModulesButNotKbnPackages = (_path) => { + if (!_path.includes('node_modules')) { + return false; + } + + return !_path.includes(`node_modules${path.sep}@kbn${path.sep}`); +}; + module.exports = { context: KIBANA_ROOT, entry: { @@ -124,7 +132,7 @@ module.exports = { }, { test: /\.scss$/, - exclude: [/node_modules/, /\.module\.s(a|c)ss$/], + exclude: [nodeModulesButNotKbnPackages, /\.module\.s(a|c)ss$/], use: [ { loader: 'style-loader', diff --git a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx index d4ccfe66dc7d4..aa032060db440 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx @@ -25,11 +25,11 @@ import { EuiIconTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FieldIcon } from '@kbn/react-field'; import classNames from 'classnames'; import { WorkspaceField } from '../../types'; import { iconChoices } from '../../helpers/style_choices'; import { LegacyIcon } from '../legacy_icon'; -import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; import { UpdateableFieldProperties } from './field_manager'; import { isEqual } from '../helpers'; diff --git a/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx index a764c0241938b..bb451211efd52 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx @@ -9,8 +9,8 @@ import React, { useState, useEffect, ReactNode } from 'react'; import { EuiPopover, EuiSelectable, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import classNames from 'classnames'; +import { FieldIcon } from '@kbn/react-field'; import { WorkspaceField } from '../../types'; -import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; export interface FieldPickerProps { fieldMap: Record<string, WorkspaceField>; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 9c22ec9d4bb05..db65916ad0754 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -36,6 +36,7 @@ import { TooltipType, } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; +import { FieldButton } from '@kbn/react-field'; import type { FieldFormatsStart } from 'src/plugins/field_formats/public'; import { EuiHighlight } from '@elastic/eui'; import { @@ -45,7 +46,6 @@ import { Filter, esQuery, } from '../../../../../src/plugins/data/public'; -import { FieldButton } from '../../../../../src/plugins/kibana_react/public'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import { DragDrop, DragDropIdentifier } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx index edb6957fcf0b2..5bb52a9ba639f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { FieldIcon, FieldIconProps } from '../../../../../src/plugins/kibana_react/public'; +import { FieldIcon, FieldIconProps } from '@kbn/react-field'; import { DataType } from '../types'; import { normalizeOperationDataType } from './utils'; diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.tsx index 77ff783f72e95..9100fd8d9b5d3 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_field_config_editor.tsx @@ -16,9 +16,9 @@ import { EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FieldIcon } from '@kbn/react-field'; import _ from 'lodash'; import { MVTFieldDescriptor } from '../../../../common/descriptor_types'; -import { FieldIcon } from '../../../../../../../src/plugins/kibana_react/public'; import { MVT_FIELD_TYPE } from '../../../../common/constants'; function makeOption({ diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx index 74269f39bc36c..8f61bfd35be85 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx @@ -16,8 +16,8 @@ import { EuiFlexItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FieldIcon } from '@kbn/react-field'; import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../../common/constants'; -import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; import { StyleField } from '../style_fields_helper'; function renderOption( diff --git a/x-pack/plugins/maps/public/components/single_field_select.tsx b/x-pack/plugins/maps/public/components/single_field_select.tsx index 67594db11eb37..8585ed46d3044 100644 --- a/x-pack/plugins/maps/public/components/single_field_select.tsx +++ b/x-pack/plugins/maps/public/components/single_field_select.tsx @@ -17,8 +17,8 @@ import { EuiFlexItem, EuiToolTip, } from '@elastic/eui'; +import { FieldIcon } from '@kbn/react-field'; import { IndexPatternField } from 'src/plugins/data/public'; -import { FieldIcon } from '../../../../../src/plugins/kibana_react/public'; function fieldsToOptions( fields?: IndexPatternField[], diff --git a/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx b/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx index 3804e3873b47e..28cca4b2f6716 100644 --- a/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx +++ b/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '@kbn/react-field'; export type FieldProps = { label: string; diff --git a/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts b/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts index a77ddaee9f250..bd72d4eb91353 100644 --- a/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts +++ b/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts @@ -7,6 +7,7 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; +import { FieldIcon } from '@kbn/react-field'; import { KibanaContextProvider, KibanaReactContextValue, @@ -15,7 +16,6 @@ import { useUiSetting$, withKibana, reactRouterNavigate, - FieldIcon, } from '../../../../../../../src/plugins/kibana_react/public'; import { StartServices } from '../../../types'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx index 327d8d963c75e..4a1cbf34990c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip } from '@elastic/eui'; import { isEmpty } from 'lodash'; +import { FieldIcon } from '@kbn/react-field'; import * as i18n from '../translations'; -import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; import { IndexPatternField } from '../../../../../../../../src/plugins/data/public'; import { getExampleText } from '../helpers'; import { BrowserField } from '../../../containers/source'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/single_field_select.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/single_field_select.tsx index 6fc10da5962ca..715246c801e5c 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/single_field_select.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/query_builder/util_components/single_field_select.tsx @@ -14,8 +14,8 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; +import { FieldIcon } from '@kbn/react-field'; import { IFieldType } from 'src/plugins/data/public'; -import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; function fieldsToOptions(fields?: IFieldType[]): Array<EuiComboBoxOptionOption<IFieldType>> { if (!fields) { diff --git a/yarn.lock b/yarn.lock index e5e2f59359c9f..8af53b75a4bb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3828,6 +3828,10 @@ version "0.0.0" uid "" +"@kbn/react-field@link:bazel-bin/packages/kbn-react-field": + version "0.0.0" + uid "" + "@kbn/rule-data-utils@link:bazel-bin/packages/kbn-rule-data-utils": version "0.0.0" uid ""