[Security Solution] Fix Timeline filter EuiSuperSelect styling (#87033)
This commit is contained in:
parent
83d9ef0f2d
commit
0c9a573515
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSuperSelect, EuiToolTip } from '@elastic/eui';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import styled, { createGlobalStyle } from 'styled-components';
|
||||
|
||||
|
@ -15,6 +15,7 @@ import { DispatchUpdateReduxTime } from '../../../../common/components/super_dat
|
|||
import { DataProvider } from '../data_providers/data_provider';
|
||||
import { QueryBarTimeline } from '../query_bar';
|
||||
|
||||
import { EuiSuperSelect } from './super_select';
|
||||
import { options } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -28,8 +29,8 @@ const SearchOrFilterGlobalStyle = createGlobalStyle`
|
|||
width: 350px !important;
|
||||
}
|
||||
|
||||
.${searchOrFilterPopoverClassName}__popoverPanel {
|
||||
width: ${searchOrFilterPopoverWidth};
|
||||
.${searchOrFilterPopoverClassName}.euiPopover__panel {
|
||||
width: ${searchOrFilterPopoverWidth} !important;
|
||||
|
||||
.euiSuperSelect__listbox {
|
||||
width: ${searchOrFilterPopoverWidth} !important;
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Duplicated EuiSuperSelect, because due to the recent changes there is no way to pass panelClassName
|
||||
prop to EuiInputPopover, which doesn't allow us to properly style the EuiInputPopover panel
|
||||
(we want the panel to be wider than the input)
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiSuperSelectProps,
|
||||
EuiScreenReaderOnly,
|
||||
EuiSuperSelectControl,
|
||||
EuiInputPopover,
|
||||
EuiContextMenuItem,
|
||||
keys,
|
||||
EuiI18n,
|
||||
} from '@elastic/eui';
|
||||
import React, { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
enum ShiftDirection {
|
||||
BACK = 'back',
|
||||
FORWARD = 'forward',
|
||||
}
|
||||
|
||||
export class EuiSuperSelect<T extends string> extends Component<EuiSuperSelectProps<T>> {
|
||||
static defaultProps = {
|
||||
compressed: false,
|
||||
fullWidth: false,
|
||||
hasDividers: false,
|
||||
isInvalid: false,
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
private itemNodes: Array<HTMLButtonElement | null> = [];
|
||||
private _isMounted: boolean = false;
|
||||
|
||||
state = {
|
||||
isPopoverOpen: this.props.isOpen || false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
if (this.props.isOpen) {
|
||||
this.openPopover();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
setItemNode = (node: HTMLButtonElement | null, index: number) => {
|
||||
this.itemNodes[index] = node;
|
||||
};
|
||||
|
||||
openPopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: true,
|
||||
});
|
||||
|
||||
const focusSelected = () => {
|
||||
const indexOfSelected = this.props.options.reduce<number | null>((acc, option, index) => {
|
||||
if (acc != null) return acc;
|
||||
if (option == null) return null;
|
||||
return option.value === this.props.valueOfSelected ? index : null;
|
||||
}, null);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.valueOfSelected != null) {
|
||||
if (indexOfSelected != null) {
|
||||
this.focusItemAt(indexOfSelected);
|
||||
} else {
|
||||
focusSelected();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
requestAnimationFrame(focusSelected);
|
||||
};
|
||||
|
||||
closePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
};
|
||||
|
||||
itemClicked = (value: T) => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
onSelectKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
if (event.key === keys.ARROW_UP || event.key === keys.ARROW_DOWN) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.openPopover();
|
||||
}
|
||||
};
|
||||
|
||||
onItemKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
switch (event.key) {
|
||||
case keys.ESCAPE:
|
||||
// close the popover and prevent ancestors from handling
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.closePopover();
|
||||
break;
|
||||
|
||||
case keys.TAB:
|
||||
// no-op
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
|
||||
case keys.ARROW_UP:
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.shiftFocus(ShiftDirection.BACK);
|
||||
break;
|
||||
|
||||
case keys.ARROW_DOWN:
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.shiftFocus(ShiftDirection.FORWARD);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
focusItemAt(index: number) {
|
||||
const targetElement = this.itemNodes[index];
|
||||
if (targetElement != null) {
|
||||
targetElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
shiftFocus(direction: ShiftDirection) {
|
||||
const currentIndex = this.itemNodes.indexOf(document.activeElement as HTMLButtonElement);
|
||||
let targetElementIndex: number;
|
||||
|
||||
if (currentIndex === -1) {
|
||||
// somehow the select options has lost focus
|
||||
targetElementIndex = 0;
|
||||
} else {
|
||||
if (direction === ShiftDirection.BACK) {
|
||||
targetElementIndex = currentIndex === 0 ? this.itemNodes.length - 1 : currentIndex - 1;
|
||||
} else {
|
||||
targetElementIndex = currentIndex === this.itemNodes.length - 1 ? 0 : currentIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.focusItemAt(targetElementIndex);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
options,
|
||||
valueOfSelected,
|
||||
onChange,
|
||||
isOpen,
|
||||
isInvalid,
|
||||
hasDividers,
|
||||
itemClassName,
|
||||
itemLayoutAlign,
|
||||
fullWidth,
|
||||
popoverClassName,
|
||||
compressed,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
const popoverClasses = classNames('euiSuperSelect', popoverClassName);
|
||||
|
||||
const buttonClasses = classNames(
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'euiSuperSelect--isOpen__button': this.state.isPopoverOpen,
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
const itemClasses = classNames(
|
||||
'euiSuperSelect__item',
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'euiSuperSelect__item--hasDividers': hasDividers,
|
||||
},
|
||||
itemClassName
|
||||
);
|
||||
|
||||
const button = (
|
||||
<EuiSuperSelectControl
|
||||
options={options}
|
||||
value={valueOfSelected}
|
||||
onClick={this.state.isPopoverOpen ? this.closePopover : this.openPopover}
|
||||
onKeyDown={this.onSelectKeyDown}
|
||||
className={buttonClasses}
|
||||
fullWidth={fullWidth}
|
||||
isInvalid={isInvalid}
|
||||
compressed={compressed}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
const items = options.map((option, index) => {
|
||||
const { value, dropdownDisplay, inputDisplay, ...optionRest } = option;
|
||||
|
||||
return (
|
||||
<EuiContextMenuItem
|
||||
key={index}
|
||||
className={itemClasses}
|
||||
icon={valueOfSelected === value ? 'check' : 'empty'}
|
||||
onClick={() => this.itemClicked(value)}
|
||||
onKeyDown={this.onItemKeyDown}
|
||||
layoutAlign={itemLayoutAlign}
|
||||
buttonRef={(node) => this.setItemNode(node, index)}
|
||||
role="option"
|
||||
id={value}
|
||||
aria-selected={valueOfSelected === value}
|
||||
{...optionRest}
|
||||
>
|
||||
{dropdownDisplay || inputDisplay}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiInputPopover
|
||||
className={popoverClasses}
|
||||
input={button}
|
||||
isOpen={isOpen || this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
panelClassName={popoverClasses}
|
||||
fullWidth={fullWidth}
|
||||
repositionOnScroll
|
||||
anchorPosition="downCenter"
|
||||
>
|
||||
<EuiScreenReaderOnly>
|
||||
<p role="alert">
|
||||
<EuiI18n
|
||||
token="euiSuperSelect.screenReaderAnnouncement"
|
||||
default="You are in a form selector of {optionsCount} items and must select a single option.
|
||||
Use the up and down keys to navigate or escape to close."
|
||||
values={{ optionsCount: options.length }}
|
||||
/>
|
||||
</p>
|
||||
</EuiScreenReaderOnly>
|
||||
<div
|
||||
className="euiSuperSelect__listbox"
|
||||
role="listbox"
|
||||
aria-activedescendant={valueOfSelected}
|
||||
tabIndex={0}
|
||||
>
|
||||
{items}
|
||||
</div>
|
||||
</EuiInputPopover>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue