[Shared UX] Solution Avatar to package (#131579)

* [Shared UX] Solution Avatar to package

* Fix imports

* Fix snapshots
This commit is contained in:
Clint Andrew Hall 2022-05-09 13:29:55 -05:00 committed by GitHub
parent 7d0db74f6a
commit 0b65d5b7e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 450 additions and 109 deletions

View file

@ -178,6 +178,7 @@
"@kbn/securitysolution-utils": "link:bazel-bin/packages/kbn-securitysolution-utils",
"@kbn/server-http-tools": "link:bazel-bin/packages/kbn-server-http-tools",
"@kbn/server-route-repository": "link:bazel-bin/packages/kbn-server-route-repository",
"@kbn/shared-ux-avatar-solution": "link:bazel-bin/packages/shared-ux/avatar/solution",
"@kbn/shared-ux-button-exit-full-screen": "link:bazel-bin/packages/shared-ux/button/exit_full_screen",
"@kbn/shared-ux-components": "link:bazel-bin/packages/kbn-shared-ux-components",
"@kbn/shared-ux-link-redirect-app": "link:bazel-bin/packages/shared-ux/link/redirect_app",
@ -666,6 +667,7 @@
"@types/kbn__securitysolution-utils": "link:bazel-bin/packages/kbn-securitysolution-utils/npm_module_types",
"@types/kbn__server-http-tools": "link:bazel-bin/packages/kbn-server-http-tools/npm_module_types",
"@types/kbn__server-route-repository": "link:bazel-bin/packages/kbn-server-route-repository/npm_module_types",
"@types/kbn__shared-ux-avatar-solution": "link:bazel-bin/packages/shared-ux/avatar/solution/npm_module_types",
"@types/kbn__shared-ux-button-exit-full-screen": "link:bazel-bin/packages/shared-ux/button/exit_full_screen/npm_module_types",
"@types/kbn__shared-ux-components": "link:bazel-bin/packages/kbn-shared-ux-components/npm_module_types",
"@types/kbn__shared-ux-link-redirect-app": "link:bazel-bin/packages/shared-ux/link/redirect_app/npm_module_types",

View file

@ -109,6 +109,7 @@ filegroup(
"//packages/kbn-ui-theme:build",
"//packages/kbn-utility-types:build",
"//packages/kbn-utils:build",
"//packages/shared-ux/avatar/solution:build",
"//packages/shared-ux/button/exit_full_screen:build",
"//packages/shared-ux/link/redirect_app:build",
],
@ -203,6 +204,7 @@ filegroup(
"//packages/kbn-ui-theme:build_types",
"//packages/kbn-utility-types:build_types",
"//packages/kbn-utils:build_types",
"//packages/shared-ux/avatar/solution:build_types",
"//packages/shared-ux/button/exit_full_screen:build_types",
"//packages/shared-ux/link/redirect_app:build_types",
],

View file

@ -42,6 +42,7 @@ NPM_MODULE_EXTRA_FILES = [
RUNTIME_DEPS = [
"//packages/kbn-i18n-react",
"//packages/kbn-i18n",
"//packages/shared-ux/avatar/solution",
"//packages/shared-ux/link/redirect_app",
"//packages/kbn-shared-ux-services",
"//packages/kbn-shared-ux-storybook",
@ -69,6 +70,7 @@ TYPES_DEPS = [
"//packages/kbn-ambient-ui-types",
"//packages/kbn-i18n-react:npm_module_types",
"//packages/kbn-i18n:npm_module_types",
"//packages/shared-ux/avatar/solution:npm_module_types",
"//packages/shared-ux/link/redirect_app:npm_module_types",
"//packages/kbn-shared-ux-services:npm_module_types",
"//packages/kbn-shared-ux-storybook:npm_module_types",

View file

@ -98,23 +98,6 @@ export const KibanaPageTemplateSolutionNavLazy = React.lazy(() =>
*/
export const KibanaPageTemplateSolutionNav = withSuspense(KibanaPageTemplateSolutionNavLazy);
/**
* The Lazily-loaded `KibanaSolutionAvatar` component. Consumers should use `React.Suspense` or
* the withSuspense` HOC to load this component.
*/
export const KibanaSolutionAvatarLazy = React.lazy(() =>
import('./solution_avatar').then(({ KibanaSolutionAvatar }) => ({
default: KibanaSolutionAvatar,
}))
);
/**
* A `KibanaSolutionAvatar` component that is wrapped by the `withSuspense` HOC. This component can
* be used directly by consumers and will load the `KibanaPageTemplateSolutionNavAvatarLazy` component lazily with
* a predefined fallback and error boundary.
*/
export const KibanaSolutionAvatar = withSuspense(KibanaSolutionAvatarLazy);
/**
* The Lazily-loaded `NoDataViews` component. Consumers should use `React.Suspennse` or the
* `withSuspense` HOC to load this component.

View file

@ -7,7 +7,7 @@ exports[`NoDataPage render 1`] = `
<EuiText
textAlign="center"
>
<KibanaSolutionAvatar
<ForwardRef
iconType="logoKibana"
name="Analytics"
size="xxl"

View file

@ -7,14 +7,15 @@
*/
import React, { useMemo, FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import classNames from 'classnames';
import { EuiLink, EuiSpacer, EuiText, EuiTextColor } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import classNames from 'classnames';
import { KibanaSolutionAvatar } from '@kbn/shared-ux-avatar-solution';
import { ElasticAgentCard } from './no_data_card';
import { NoDataPageProps } from './types';
import { KibanaSolutionAvatar } from '../../solution_avatar';
export const NoDataPage: FunctionComponent<NoDataPageProps> = ({
solution,

View file

@ -178,7 +178,7 @@ exports[`KibanaPageTemplateSolutionNav renders with icon 1`] = `
className="kbnPageTemplateSolutionNav"
heading={
<React.Fragment>
<KibanaSolutionAvatar
<ForwardRef
className="kbnPageTemplateSolutionNavAvatar"
iconType="logoElastic"
name="Solution"
@ -245,7 +245,7 @@ exports[`KibanaPageTemplateSolutionNav renders with icon 1`] = `
}
mobileTitle={
<React.Fragment>
<KibanaSolutionAvatar
<ForwardRef
className="kbnPageTemplateSolutionNavAvatar"
iconType="logoElastic"
name="Solution"

View file

@ -8,8 +8,7 @@
import './solution_nav.scss';
import React, { FunctionComponent, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import classNames from 'classnames';
import {
EuiAvatarProps,
EuiFlyout,
@ -19,8 +18,9 @@ import {
useIsWithinBreakpoints,
} from '@elastic/eui';
import classNames from 'classnames';
import { KibanaSolutionAvatar } from '../../solution_avatar';
import { FormattedMessage } from '@kbn/i18n-react';
import { KibanaSolutionAvatar } from '@kbn/shared-ux-avatar-solution';
import { KibanaPageTemplateSolutionNavCollapseButton } from './solution_nav_collapse_button';
export type KibanaPageTemplateSolutionNavProps = EuiSideNavProps<{}> & {

View file

@ -1,33 +0,0 @@
/*
* 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 { KibanaSolutionAvatar, KibanaSolutionAvatarProps } from './solution_avatar';
export default {
title: 'Solution Avatar',
description: 'A wrapper around EuiAvatar, specifically to stylize Elastic Solutions',
};
type Params = Pick<KibanaSolutionAvatarProps, 'size' | 'name'>;
export const PureComponent = (params: Params) => {
return <KibanaSolutionAvatar {...params} />;
};
PureComponent.argTypes = {
name: {
control: 'text',
defaultValue: 'Kibana',
},
size: {
control: 'radio',
options: ['s', 'm', 'l', 'xl', 'xxl'],
defaultValue: 'xxl',
},
};

View file

@ -1,44 +0,0 @@
/*
* 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 './solution_avatar.scss';
import React from 'react';
import { DistributiveOmit, EuiAvatar, EuiAvatarProps } from '@elastic/eui';
import classNames from 'classnames';
export type KibanaSolutionAvatarProps = DistributiveOmit<EuiAvatarProps, 'size'> & {
/**
* Any EuiAvatar size available, or `xxl` for custom large, brand-focused version
*/
size?: EuiAvatarProps['size'] | 'xxl';
};
/**
* Applies extra styling to a typical EuiAvatar.
* The `name` value will be appended to 'logo' to configure the `iconType` unless `iconType` is provided.
*/
export const KibanaSolutionAvatar = ({ className, size, ...rest }: KibanaSolutionAvatarProps) => {
return (
// @ts-ignore Complains about ExclusiveUnion between `iconSize` and `iconType`, but works fine
<EuiAvatar
className={classNames(
'kbnSolutionAvatar',
{
[`kbnSolutionAvatar--${size}`]: size,
},
className
)}
size={size === 'xxl' ? 'xl' : size}
iconSize={size}
color="plain"
iconType={`logo${rest.name}`}
{...rest}
/>
);
};

View file

@ -0,0 +1,146 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "solution"
PKG_REQUIRE_NAME = "@kbn/shared-ux-avatar-solution"
SOURCE_FILES = glob(
[
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.scss",
"src/**/*.mdx",
"src/**/*.svg",
],
exclude = [
"**/*.test.*",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
# In this array place runtime dependencies, including other packages and NPM packages
# which must be available for this code to run.
#
# To reference other packages use:
# "//repo/relative/path/to/package"
# eg. "//packages/kbn-utils"
#
# To reference a NPM package use:
# "@npm//name-of-package"
# eg. "@npm//lodash"
RUNTIME_DEPS = [
"@npm//@elastic/eui",
"@npm//classnames",
"@npm//enzyme",
"@npm//react",
"@npm//url-loader",
"//packages/kbn-i18n-react",
"//packages/kbn-i18n",
"//packages/kbn-shared-ux-utility",
]
# In this array place dependencies necessary to build the types, which will include the
# :npm_module_types target of other packages and packages from NPM, including @types/*
# packages.
#
# To reference the types for another package use:
# "//repo/relative/path/to/package:npm_module_types"
# eg. "//packages/kbn-utils:npm_module_types"
#
# References to NPM packages work the same as RUNTIME_DEPS
TYPES_DEPS = [
"@npm//@elastic/eui",
"@npm//@storybook/addon-actions",
"@npm//@types/classnames",
"@npm//@types/enzyme",
"@npm//@types/jest",
"@npm//@types/node",
"@npm//@types/react",
"//packages/kbn-ambient-ui-types",
"//packages/kbn-i18n-react:npm_module_types",
"//packages/kbn-i18n:npm_module_types",
"//packages/kbn-shared-ux-utility:npm_module_types",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
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,
emit_declaration_only = True,
out_dir = "target_types",
root_dir = "src",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm_types(
name = "npm_module_types",
srcs = SRCS,
deps = [":tsc_types"],
package_name = PKG_REQUIRE_NAME,
tsconfig = ":tsconfig",
visibility = ["//visibility:public"],
)
filegroup(
name = "build_types",
srcs = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,26 @@
---
id: sharedUX/Components/KibanaSolutionAvatar
slug: /shared-ux/components/avatar-solution
title: Solution Avatar
summary: A wrapper around `EuiAvatar` tailored for use in Kibana solutions.
tags: ['shared-ux', 'component']
date: 2022-05-04
---
## Description
A wrapper around `EuiAvatar` tailored for use in Kibana solutions.
## Usage
If using for a known solution, (e.g. one whose logo is in EUI as `logoSomeSolution`), you can simply set the `name` prop:
```tsx
<KibanaSolutionAvatar name="Kibana" size="xl" />
```
If the name provided does not match a known solution, you *must* set the `iconType` prop:
```tsx
<KibanaSolutionAvatar name="Hello World!" size="xl" iconType="logoElastic" />
```

View file

@ -5,5 +5,9 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { KibanaSolutionAvatar } from './solution_avatar';
export type { KibanaSolutionAvatarProps } from './solution_avatar';
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/packages/shared-ux/avatar/solution'],
};

View file

@ -0,0 +1,8 @@
{
"name": "@kbn/shared-ux-avatar-solution",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"browser": "./target_web/index.js",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -8,3 +8,12 @@ exports[`KibanaSolutionAvatar renders 1`] = `
name="Solution"
/>
`;
exports[`KibanaSolutionAvatar renders 2`] = `
<EuiAvatar
className="kbnSolutionAvatar"
color="plain"
iconType="logoElasticStack"
name="Elastic Stack"
/>
`;

View file

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Before After
Before After

View file

@ -0,0 +1,29 @@
/*
* 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 { withSuspense } from '@kbn/shared-ux-utility';
export type { KibanaSolutionAvatarProps } from './solution_avatar';
/**
* The Lazily-loaded `KibanaSolutionAvatar` component. Consumers should use `React.Suspense` or
* the withSuspense` HOC to load this component.
*/
export const KibanaSolutionAvatarLazy = React.lazy(() =>
import('./solution_avatar').then(({ KibanaSolutionAvatar }) => ({
default: KibanaSolutionAvatar,
}))
);
/**
* A `KibanaSolutionAvatar` component that is wrapped by the `withSuspense` HOC. This component can
* be used directly by consumers and will load the `KibanaPageTemplateSolutionNavAvatarLazy` component lazily with
* a predefined fallback and error boundary.
*/
export const KibanaSolutionAvatar = withSuspense(KibanaSolutionAvatarLazy);

View file

@ -0,0 +1,69 @@
/*
* 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 { KibanaSolutionAvatar, IconTypeProps, KnownSolutionProps } from './solution_avatar';
export default {
title: 'Solution Avatar',
description: 'A wrapper around EuiAvatar, specifically to stylize Elastic Solutions',
};
const argTypes = {
size: {
control: 'select',
options: ['s', 'm', 'l', 'xl', 'xxl'],
defaultValue: 'xxl',
},
};
type KnownSolutionParams = Pick<KnownSolutionProps, 'size' | 'name'>;
export const SolutionAvatar = (params: KnownSolutionParams) => {
return <KibanaSolutionAvatar {...params} />;
};
SolutionAvatar.argTypes = {
name: {
control: 'select',
options: ['Cloud', 'Elastic', 'Kibana', 'Observability', 'Security', 'Enterprise Search'],
defaultValue: 'Elastic',
},
...argTypes,
};
type IconTypeParams = Pick<IconTypeProps, 'size' | 'name' | 'iconType'>;
export const IconTypeAvatar = (params: IconTypeParams) => {
return <KibanaSolutionAvatar {...params} />;
};
IconTypeAvatar.argTypes = {
iconType: {
control: 'select',
options: [
'logoCloud',
'logoElastic',
'logoElasticsearch',
'logoElasticStack',
'logoKibana',
'logoObservability',
'logoSecurity',
'logoSiteSearch',
'logoWorkplaceSearch',
'machineLearningApp',
'managementApp',
],
defaultValue: 'logoElastic',
},
name: {
control: 'text',
defaultValue: 'Solution Name',
},
...argTypes,
};

View file

@ -12,7 +12,9 @@ import { KibanaSolutionAvatar } from './solution_avatar';
describe('KibanaSolutionAvatar', () => {
test('renders', () => {
const component = shallow(<KibanaSolutionAvatar name="Solution" iconType="logoElastic" />);
expect(component).toMatchSnapshot();
const nameAndIcon = shallow(<KibanaSolutionAvatar name="Solution" iconType="logoElastic" />);
expect(nameAndIcon).toMatchSnapshot();
const nameOnly = shallow(<KibanaSolutionAvatar name="Elastic Stack" />);
expect(nameOnly).toMatchSnapshot();
});
});

View file

@ -0,0 +1,74 @@
/*
* 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 './solution_avatar.scss';
import React from 'react';
import classNames from 'classnames';
import { DistributiveOmit, EuiAvatar, EuiAvatarProps, IconType } from '@elastic/eui';
import { SolutionNameType } from './types';
export type KnownSolutionProps = DistributiveOmit<EuiAvatarProps, 'size' | 'name' | 'iconType'> & {
/**
* Any EuiAvatar size available, or `xxl` for custom large, brand-focused version
*/
size?: EuiAvatarProps['size'] | 'xxl';
name: SolutionNameType;
};
export type IconTypeProps = DistributiveOmit<EuiAvatarProps, 'size' | 'name' | 'iconType'> & {
/**
* Any EuiAvatar size available, or `xxl` for custom large, brand-focused version
*/
size?: EuiAvatarProps['size'] | 'xxl';
name?: string;
iconType: IconType;
};
const isKnown = (props: any): props is KnownSolutionProps => {
return typeof props.iconType === 'undefined';
};
export type KibanaSolutionAvatarProps = KnownSolutionProps | IconTypeProps;
/**
* Applies extra styling to a typical EuiAvatar.
* The `name` value will be appended to 'logo' to configure the `iconType` unless `iconType` is provided.
*/
export const KibanaSolutionAvatar = (props: KibanaSolutionAvatarProps) => {
const { className, size, ...rest } = props;
// If the name is a known solution, use the name to set the correct IconType.
// Create an empty object so `iconType` remains undefined or inherited from `props`.
const icon: {
iconType?: IconType;
} = {};
if (isKnown(props)) {
icon.iconType = `logo${props.name.replace(/\s+/g, '')}`;
}
return (
// @ts-ignore Complains about ExclusiveUnion between `iconSize` and `iconType`, but works fine
<EuiAvatar
className={classNames(
'kbnSolutionAvatar',
{
[`kbnSolutionAvatar--${size}`]: size,
},
className
)}
size={size === 'xxl' ? 'xl' : size}
iconSize={size}
color="plain"
{...rest}
{...icon}
/>
);
};

View file

@ -0,0 +1,34 @@
/*
* 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.
*/
// Manual, exhaustive list at present. This was attempted dynamically using Typescript Template Literals and
// the computation cost exceeded the benefit. By enumerating them manually, we reduce the complexity of TS
// checking at the expense of not being dynamic against a very, very static list.
//
// The only consequence is requiring a solution name without a space, (e.g. `ElasticStack`) until it's added
// here. That's easy to do in the very unlikely event that ever happens.
export type SolutionNameType =
| 'App Search'
| 'Beats'
| 'Business Analytics'
| 'Cloud'
| 'Cloud Enterprise'
| 'Code'
| 'Elastic'
| 'Elastic Stack'
| 'Elasticsearch'
| 'Enterprise Search'
| 'Logstash'
| 'Maps'
| 'Metrics'
| 'Observability'
| 'Security'
| 'Site Search'
| 'Uptime'
| 'Webhook'
| 'Workplace Search';

View file

@ -0,0 +1,19 @@
{
"extends": "../../../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"rootDir": "src",
"stripInternal": false,
"types": [
"jest",
"node",
"react",
"@kbn/ambient-ui-types"
]
},
"include": [
"src/**/*"
]
}

View file

@ -23,7 +23,7 @@ import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { KibanaPageTemplate, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaSolutionAvatar } from '@kbn/shared-ux-components';
import { KibanaSolutionAvatar } from '@kbn/shared-ux-avatar-solution';
import type { Space } from '../../common';
import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../common/constants';

View file

@ -3204,6 +3204,10 @@
version "0.0.0"
uid ""
"@kbn/shared-ux-avatar-solution@link:bazel-bin/packages/shared-ux/avatar/solution":
version "0.0.0"
uid ""
"@kbn/shared-ux-button-exit-full-screen@link:bazel-bin/packages/shared-ux/button/exit_full_screen":
version "0.0.0"
uid ""
@ -6296,6 +6300,10 @@
version "0.0.0"
uid ""
"@types/kbn__shared-ux-avatar-solution@link:bazel-bin/packages/shared-ux/avatar/solution/npm_module_types":
version "0.0.0"
uid ""
"@types/kbn__shared-ux-button-exit-full-screen@link:bazel-bin/packages/shared-ux/button/exit_full_screen/npm_module_types":
version "0.0.0"
uid ""