Updates to K7 nav drawer and EUI to 9.4.0 (#32864)

* update for new eui nav drawer

* remove extraAction on flyout

* fix mobile menu toggle

* add i18n for recently reviewed nav link

* progress on updating tests

* replace EuiListGroup with EuiNavDrawerGroup

* Quick label fixes

- Removing aria-label if the same as label since the `EuiNavDrawerGroup` takes care of that
- Truncating any recent items to just 64 characters and applying the `title` attribute to display the whole label.

* Adding a truncation buffer

* fix navDrawerRef

* Update to EUI 9.3.0

Also comments out some logos that don’t exist that were throwing console errors

* remove unused import

* typo fix

* One DTS too many in test

* …all the small things…

* Upgrade plugin dependencies and fix nav drawer test

* update for new eui nav drawer

* remove extraAction on flyout

* fix mobile menu toggle

* add i18n for recently reviewed nav link

* progress on updating tests

* replace EuiListGroup with EuiNavDrawerGroup

* Quick label fixes

- Removing aria-label if the same as label since the `EuiNavDrawerGroup` takes care of that
- Truncating any recent items to just 64 characters and applying the `title` attribute to display the whole label.

* Adding a truncation buffer

* fix navDrawerRef

* Update to EUI 9.3.0

Also comments out some logos that don’t exist that were throwing console errors

* remove unused import

* typo fix

* One DTS too many in test

* …all the small things…

* Upgrade plugin dependencies and fix nav drawer test

* Add support for normal image paths

Updated to EUI 9.4.0

* update testing appMenu.clickLink selector

* leave appsMenu closed while reading and clicking links

* use saveVisualizationExpectSuccess to ensure modal is closed before continuing
This commit is contained in:
Ryan Keairns 2019-03-14 12:35:21 -05:00 committed by Caroline Horn
parent 0ebb126560
commit e9885e200a
14 changed files with 102 additions and 319 deletions

View file

@ -95,7 +95,7 @@
},
"dependencies": {
"@elastic/datemath": "5.0.2",
"@elastic/eui": "9.2.1",
"@elastic/eui": "9.4.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.3.2",

View file

@ -82,7 +82,7 @@ It allows you to monitor the performance of thousands of applications in real ti
'{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html',
},
}),
euiIconType: 'apmApp',
euiIconType: 'logoAPM',
artifacts: artifacts,
onPrem: onPremInstructions(config),
elasticCloud: createElasticCloudInstructions(config),

View file

@ -43,7 +43,7 @@ export function natsLogsSpecProvider(server, context) {
learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-nats.html',
},
}),
euiIconType: 'logoNats',
// euiIconType: 'logoNats',
artifacts: {
dashboards: [
{

View file

@ -39,7 +39,7 @@ export function natsMetricsSpecProvider(server, context) {
learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-nats.html',
},
}),
euiIconType: 'logoNats',
// euiIconType: 'logoNats',
artifacts: {
dashboards: [
{

View file

@ -28,6 +28,7 @@ export interface NavLink {
subUrlBase: string;
id: string;
euiIconType: IconType;
icon?: string;
active: boolean;
lastSubUrl?: string;
hidden?: boolean;

View file

@ -19,8 +19,7 @@
import Url from 'url';
import classNames from 'classnames';
import React, { Component, Fragment } from 'react';
import React, { Component, createRef, Fragment } from 'react';
import * as Rx from 'rxjs';
import {
@ -39,16 +38,14 @@ import {
EuiHideFor,
EuiHorizontalRule,
EuiIcon,
EuiListGroup,
// @ts-ignore
EuiImage,
// @ts-ignore
EuiListGroupItem,
// @ts-ignore
EuiNavDrawer,
// @ts-ignore
EuiNavDrawerFlyout,
// @ts-ignore
EuiNavDrawerMenu,
EuiOutsideClickDetector,
EuiNavDrawerGroup,
// @ts-ignore
EuiShowFor,
} from '@elastic/eui';
@ -79,6 +76,11 @@ interface Props {
intl: InjectedIntl;
}
// Providing a buffer between the limit and the cut off index
// protects from truncating just the last couple (6) characters
const TRUNCATE_LIMIT: number = 64;
const TRUNCATE_AT: number = 58;
function extendRecentlyAccessedHistoryItem(
navLinks: NavLink[],
recentlyAccessed: RecentlyAccessedHistoryItem
@ -115,16 +117,15 @@ function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void {
}
}
function truncateRecentItemLabel(label: string): string {
if (label.length > TRUNCATE_LIMIT) {
label = `${label.substring(0, TRUNCATE_AT)}`;
}
return label;
}
interface State {
isCollapsed: boolean;
flyoutIsCollapsed: boolean;
flyoutIsAnimating: boolean;
navFlyoutTitle: string;
navFlyoutContent: [];
mobileIsHidden: boolean;
showScrollbar: boolean;
outsideClickDisabled: boolean;
isManagingFocus: boolean;
navLinks: Array<ReturnType<typeof extendNavLink>>;
recentlyAccessed: Array<ReturnType<typeof extendRecentlyAccessedHistoryItem>>;
forceNavigation: boolean;
@ -132,23 +133,12 @@ interface State {
class HeaderUI extends Component<Props, State> {
private subscription?: Rx.Subscription;
private timeoutID?: ReturnType<typeof setTimeout>;
private timeoutExpand?: ReturnType<typeof setTimeout>;
private timeoutScrollbar?: ReturnType<typeof setTimeout>;
private navDrawerRef = createRef<EuiNavDrawer>();
constructor(props: Props) {
super(props);
this.state = {
isCollapsed: true,
flyoutIsCollapsed: true,
flyoutIsAnimating: false,
navFlyoutTitle: '',
navFlyoutContent: [],
mobileIsHidden: true,
showScrollbar: false,
outsideClickDisabled: true,
isManagingFocus: false,
navLinks: [],
recentlyAccessed: [],
forceNavigation: false,
@ -197,14 +187,17 @@ class HeaderUI extends Component<Props, State> {
public renderMenuTrigger() {
return (
<EuiHeaderSectionItemButton aria-label="Toggle side navigation" onClick={this.toggleOpen}>
<EuiHeaderSectionItemButton
aria-label="Toggle side navigation"
onClick={() => this.navDrawerRef.current.toggleOpen()}
>
<EuiIcon type="apps" size="m" />
</EuiHeaderSectionItemButton>
);
}
public render() {
const { appTitle, breadcrumbs$, isVisible, navControls, helpExtension$ } = this.props;
const { appTitle, breadcrumbs$, isVisible, navControls, helpExtension$, intl } = this.props;
const { navLinks, recentlyAccessed } = this.state;
if (!isVisible) {
@ -214,6 +207,52 @@ class HeaderUI extends Component<Props, State> {
const leftNavControls = navControls.bySide[NavControlSide.Left];
const rightNavControls = navControls.bySide[NavControlSide.Right];
let navLinksArray = navLinks.map(navLink =>
navLink.hidden
? null
: {
key: navLink.id,
label: navLink.title,
href: navLink.href,
iconType: navLink.euiIconType,
icon:
!navLink.euiIconType && navLink.icon ? (
<EuiImage size="s" alt="" aria-hidden={true} url={`/${navLink.icon}`} />
) : (
undefined
),
isActive: navLink.active,
'data-test-subj': 'navDrawerAppsMenuLink',
}
);
// filter out the null items
navLinksArray = navLinksArray.filter(item => item !== null);
const recentLinksArray = [
{
label: intl.formatMessage({
id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsLabel',
defaultMessage: 'Recently viewed',
}),
iconType: 'clock',
isDisabled: recentlyAccessed.length > 0 ? false : true,
flyoutMenu: {
title: intl.formatMessage({
id: 'common.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle',
defaultMessage: 'Recent items',
}),
listItems: recentlyAccessed.map(item => ({
label: truncateRecentItemLabel(item.label),
// TODO: Add what type of app/saved object to title attr
title: `${item.label}`,
'aria-label': item.label,
href: item.href,
iconType: item.euiIconType,
})),
},
},
];
return (
<Fragment>
<EuiHeader>
@ -238,85 +277,11 @@ class HeaderUI extends Component<Props, State> {
</EuiHeaderSection>
</EuiHeader>
<EuiOutsideClickDetector
onOutsideClick={() => this.collapseDrawer()}
isDisabled={this.state.outsideClickDisabled}
>
<EuiNavDrawer
isCollapsed={this.state.isCollapsed}
flyoutIsCollapsed={this.state.flyoutIsCollapsed}
flyoutIsAnimating={this.state.flyoutIsAnimating}
onMouseEnter={this.expandDrawer}
onFocus={this.expandDrawer}
onBlur={this.focusOut}
onMouseLeave={this.collapseDrawer}
mobileIsHidden={this.state.mobileIsHidden}
showScrollbar={this.state.showScrollbar}
data-test-subj={classNames(
'navDrawer',
this.state.flyoutIsAnimating
? null
: this.state.isCollapsed
? 'collapsed'
: 'expanded'
)}
>
<EuiNavDrawerMenu id="navDrawerMenu" onClick={this.onNavClick}>
<EuiListGroup>
<EuiListGroupItem
label="Recently viewed"
iconType="clock"
size="s"
style={{ color: 'inherit' }}
aria-label="Recently viewed items"
onClick={() => this.expandFlyout()}
isDisabled={recentlyAccessed.length > 0 ? false : true}
extraAction={{
color: 'subdued',
iconType: 'arrowRight',
iconSize: 's',
'aria-label': 'Expand to view recent apps and objects',
onClick: () => this.expandFlyout(),
alwaysShow: true,
}}
/>
</EuiListGroup>
<EuiHorizontalRule margin="none" />
<EuiListGroup data-test-subj="appsMenu">
{navLinks.map(navLink =>
navLink.hidden ? null : (
<EuiListGroupItem
key={navLink.id}
label={navLink.title}
href={navLink.href}
iconType={navLink.euiIconType}
size="s"
style={{ color: 'inherit' }}
aria-label={navLink.title}
isActive={navLink.active}
data-test-subj="appLink"
/>
)
)}
</EuiListGroup>
</EuiNavDrawerMenu>
<EuiNavDrawerFlyout
id="navDrawerFlyout"
title="Recent items"
isCollapsed={this.state.flyoutIsCollapsed}
listItems={recentlyAccessed.map(item => ({
label: item.label,
href: item.href,
iconType: item.euiIconType,
size: 's',
style: { color: 'inherit' },
'aria-label': item.label,
}))}
onMouseLeave={this.collapseFlyout}
wrapText={true}
/>
</EuiNavDrawer>
</EuiOutsideClickDetector>
<EuiNavDrawer ref={this.navDrawerRef} data-test-subj="navDrawer">
<EuiNavDrawerGroup listItems={recentLinksArray} />
<EuiHorizontalRule margin="none" />
<EuiNavDrawerGroup data-test-subj="navDrawerAppsMenu" listItems={navLinksArray} />
</EuiNavDrawer>
</Fragment>
);
}
@ -361,120 +326,6 @@ class HeaderUI extends Component<Props, State> {
event.stopPropagation();
}
};
private toggleOpen = () => {
this.setState({
mobileIsHidden: !this.state.mobileIsHidden,
});
setTimeout(() => {
this.setState({
outsideClickDisabled: this.state.mobileIsHidden ? true : false,
});
}, this.getTimeoutMs(350));
};
private expandDrawer = () => {
this.timeoutExpand = setTimeout(() => {
this.setState({
isCollapsed: false,
});
}, this.getTimeoutMs(750));
this.timeoutScrollbar = setTimeout(() => {
this.setState({
showScrollbar: true,
});
}, this.getTimeoutMs(1200));
// This prevents the drawer from collapsing when tabbing through children
// by clearing the timeout thus cancelling the onBlur event (see focusOut).
// This means isManagingFocus remains true as long as a child element
// has focus. This is the case since React bubbles up onFocus and onBlur
// events from the child elements.
if (this.timeoutID) {
clearTimeout(this.timeoutID);
}
if (!this.state.isManagingFocus) {
this.setState({
isManagingFocus: true,
});
}
};
private collapseDrawer = () => {
// Stop the expand animation
if (this.timeoutExpand) {
clearTimeout(this.timeoutExpand);
}
if (this.timeoutScrollbar) {
clearTimeout(this.timeoutScrollbar);
}
this.setState({
flyoutIsAnimating: false,
isCollapsed: true,
flyoutIsCollapsed: true,
mobileIsHidden: true,
showScrollbar: false,
outsideClickDisabled: true,
});
// Scrolls the menu and flyout back to top when the nav drawer collapses
const menuEl = document.getElementById('navDrawerMenu');
if (menuEl) {
menuEl.scrollTop = 0;
}
const flyoutEl = document.getElementById('navDrawerFlyout');
if (flyoutEl) {
flyoutEl.scrollTop = 0;
}
};
private focusOut = () => {
// This collapses the drawer when no children have focus (i.e. tabbed out).
// In other words, if focus does not bubble up from a child element, then
// the drawer will collapse. See the corresponding block in expandDrawer
// (called by onFocus) which cancels this operation via clearTimeout.
this.timeoutID = setTimeout(() => {
if (this.state.isManagingFocus) {
this.setState({
isManagingFocus: false,
});
this.collapseDrawer();
}
}, 0);
};
private expandFlyout = () => {
this.setState(() => ({
flyoutIsCollapsed: !this.state.flyoutIsCollapsed,
}));
this.setState({
flyoutIsAnimating: true,
});
};
private collapseFlyout = () => {
this.setState({ flyoutIsAnimating: true });
setTimeout(() => {
this.setState({
flyoutIsCollapsed: true,
});
}, this.getTimeoutMs(250));
};
private getTimeoutMs = (defaultTimeout: number) => {
const uiSettings = chrome.getUiSettingsClient();
return uiSettings.get('accessibility:disableAnimations') ? 0 : defaultTimeout;
};
}
export const Header = injectI18n(HeaderUI);

View file

@ -283,7 +283,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.waitUntilLoadingHasFinished();
await pieChart.expectPieSliceCount(5);
await PageObjects.visualize.saveVisualization('Rendering Test: animal sounds pie');
await PageObjects.visualize.saveVisualizationExpectSuccess('Rendering Test: animal sounds pie');
await PageObjects.header.clickDashboard();
await pieChart.expectPieSliceCount(5);

View file

@ -22,22 +22,19 @@ import { FtrProviderContext } from '../ftr_provider_context';
export function AppsMenuProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const log = getService('log');
const retry = getService('retry');
const globalNav = getService('globalNav');
return new class AppsMenu {
/**
* Get the text and href from each of the links in the apps menu
*/
public async readLinks() {
await this.ensureMenuOpen();
const appMenu = await testSubjects.find('navDrawer&expanded appsMenu');
const appMenu = await testSubjects.find('navDrawer');
const $ = await appMenu.parseDomContent();
const links: Array<{
text: string;
href: string;
}> = $.findTestSubjects('appLink')
}> = $.findTestSubjects('navDrawerAppsMenuLink')
.toArray()
.map((link: any) => {
return {
@ -46,7 +43,6 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) {
};
});
await this.ensureMenuClosed();
return links;
}
@ -65,30 +61,12 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) {
public async clickLink(name: string) {
try {
log.debug(`click "${name}" app link`);
await this.ensureMenuOpen();
const container = await testSubjects.find('navDrawer&expanded appsMenu');
const link = await container.findByPartialLinkText(name);
const container = await testSubjects.find('navDrawer');
// Text content is not visible or selectable (0px width) so we use an attr with th same value
const link = await container.findByCssSelector(`[aria-label='${name}']`);
await link.click();
} finally {
await this.ensureMenuClosed();
}
}
private async ensureMenuClosed() {
await globalNav.moveMouseToLogo();
await retry.waitFor(
'apps drawer closed',
async () => await testSubjects.exists('navDrawer&collapsed')
);
}
private async ensureMenuOpen() {
if (!(await testSubjects.exists('navDrawer&expanded'))) {
await testSubjects.moveMouseTo('navDrawer');
await retry.waitFor(
'apps drawer open',
async () => await testSubjects.exists('navDrawer&expanded')
);
// Intentionally empty
}
}
}();

View file

@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@elastic/eui": "5.0.0",
"react": "^16.3.0"
"@elastic/eui": "9.4.0",
"react": "^16.8.0"
}
}

View file

@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@elastic/eui": "5.0.0",
"react": "^16.3.0"
"@elastic/eui": "9.4.0",
"react": "^16.8.0"
}
}

View file

@ -7,8 +7,8 @@
},
"license": "Apache-2.0",
"dependencies": {
"@elastic/eui": "5.0.0",
"react": "^16.3.0",
"react-dom": "^16.3.0"
"@elastic/eui": "9.4.0",
"react": "^16.8.0",
"react-dom": "^16.8.0"
}
}

View file

@ -30,7 +30,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.common.navigateToApp('settings');
});
it('should should nav link that navigates to the app', async () => {
it('should show nav link that navigates to the app', async () => {
await appsMenu.clickLink('Test Plugin App');
const pluginContent = await testSubjects.find('pluginContent');
expect(await pluginContent.getVisibleText()).to.be('Super simple app plugin');

View file

@ -136,7 +136,7 @@
},
"dependencies": {
"@elastic/datemath": "5.0.2",
"@elastic/eui": "9.2.1",
"@elastic/eui": "9.4.0",
"@elastic/node-crypto": "0.1.2",
"@elastic/numeral": "2.3.2",
"@kbn/babel-preset": "1.0.0",

View file

@ -796,34 +796,10 @@
tabbable "^1.1.0"
uuid "^3.1.0"
"@elastic/eui@5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-5.0.0.tgz#e6fe9e1aa8b00c93045178f78a6dd0d457d56fa8"
integrity sha512-WL6sp6u2Rt1O7a2exLU/RuDcRnpluPN6aQ2JexBl+G6mVyF8F5I3RGJKTJp3jOozOaODRY2ev+Nq57EydkjrKg==
dependencies:
classnames "^2.2.5"
core-js "^2.5.1"
focus-trap-react "^3.0.4"
highlight.js "^9.12.0"
html "^1.0.0"
keymirror "^0.1.1"
lodash "npm:@elastic/lodash@3.10.1-kibana1"
numeral "^2.0.6"
prop-types "^15.6.0"
react-ace "^5.5.0"
react-color "^2.13.8"
react-datepicker v1.5.0
react-input-autosize "^2.2.1"
react-virtualized "^9.18.5"
react-vis "1.10.2"
resize-observer-polyfill "^1.5.0"
tabbable "^1.1.0"
uuid "^3.1.0"
"@elastic/eui@9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.2.1.tgz#3860a7219f0ee8f33ec43447edb2eda3c96a00a7"
integrity sha512-U92s0nh6vBS6NBiHDCq6e+49Dam8It+Iy81b2NProf1OIRk/nB0RE781x6zgUzmOFpJ1T0xiThWiiDP776v0LQ==
"@elastic/eui@9.4.0":
version "9.4.0"
resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-9.4.0.tgz#e5f83c612674fc6f59e990f557e563be1409d25e"
integrity sha512-cQnsU3UeQ1s6F427vY53lzlh7mvNoPmIvPL24B5jH3JYaWULhIL1xSJXJgDSOSdrdHCxkXDD9Q8Y7256x4Xf4Q==
dependencies:
"@types/lodash" "^4.14.116"
"@types/numeral" "^0.0.25"
@ -18132,11 +18108,6 @@ polished@^1.9.2:
resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.2.tgz#d705cac66f3a3ed1bd38aad863e2c1e269baf6b6"
integrity sha512-mPocQrVUSiqQdHNZFGL1iHJmsR/etiv05Nf2oZUbya+GMsQkZVEBl5wonN+Sr/e9zQBEhT6yrMjxAUJ06eyocQ==
popper.js@^1.14.1:
version "1.14.3"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095"
integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=
portfinder@^1.0.9:
version "1.0.13"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
@ -18997,16 +18968,6 @@ react-color@^2.13.8, react-color@^2.14.1:
reactcss "^1.2.0"
tinycolor2 "^1.4.1"
react-datepicker@v1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-1.5.0.tgz#7eacd9609313189c84a21bb7421486054939a4b2"
integrity sha512-Neh1rz0d1QeR7KuoTiYeR6oj73DJkqt0vuNSgfMuxXEwGmz/4sPynouYGo6gdKiQbxIXBJJ/FLDLHJEr5XNThw==
dependencies:
classnames "^2.2.5"
prop-types "^15.6.0"
react-onclickoutside "^6.7.1"
react-popper "^0.9.1"
react-datetime@^2.14.0:
version "2.15.0"
resolved "https://registry.yarnpkg.com/react-datetime/-/react-datetime-2.15.0.tgz#a8f7da6c58b6b45dbeea32d4e8485db17614e12c"
@ -19087,7 +19048,7 @@ react-docgen@^3.0.0-rc.1:
node-dir "^0.1.10"
recast "^0.16.0"
react-dom@^16.2.0, react-dom@^16.3.0, react-dom@^16.8.0:
react-dom@^16.2.0, react-dom@^16.8.0:
version "16.8.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.2.tgz#7c8a69545dd554d45d66442230ba04a6a0a3c3d3"
integrity sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==
@ -19258,19 +19219,11 @@ react-motion@^0.5.2:
prop-types "^15.5.8"
raf "^3.1.0"
react-onclickoutside@^6.5.0, react-onclickoutside@^6.7.1:
react-onclickoutside@^6.5.0:
version "6.7.1"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz#6a5b5b8b4eae6b776259712c89c8a2b36b17be93"
integrity sha512-p84kBqGaMoa7VYT0vZ/aOYRfJB+gw34yjpda1Z5KeLflg70HipZOT+MXQenEhdkPAABuE2Astq4zEPdMqUQxcg==
react-popper@^0.9.1:
version "0.9.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-0.9.5.tgz#02a24ef3eec33af9e54e8358ab70eb0e331edd05"
integrity sha1-AqJO8+7DOvnlToNYq3DrDjMe3QU=
dependencies:
popper.js "^1.14.1"
prop-types "^15.6.1"
react-portal@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-3.2.0.tgz#4224e19b2b05d5cbe730a7ba0e34ec7585de0043"
@ -19536,7 +19489,7 @@ react-vis@^1.8.1:
prop-types "^15.5.8"
react-motion "^0.4.8"
react@^16.2.0, react@^16.3.0, react@^16.6.0, react@^16.8.0:
react@^16.2.0, react@^16.6.0, react@^16.8.0:
version "16.8.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c"
integrity sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==