44 * you may not use this file except in compliance with the Elastic License.
55 */
66
7- /* eslint-disable react/display-name */
8-
97import { i18n } from '@kbn/i18n' ;
10- import React , { useState , useCallback , useRef , useLayoutEffect , useMemo } from 'react' ;
11- import { EuiI18nNumber , EuiButton , EuiPopover , ButtonColor } from '@elastic/eui' ;
8+ import React , { useMemo } from 'react' ;
9+ import { EuiI18nNumber } from '@elastic/eui' ;
1210import styled from 'styled-components' ;
1311import { ResolverNodeStats } from '../../../common/endpoint/types' ;
1412import { useRelatedEventByCategoryNavigation } from './use_related_event_by_category_navigation' ;
15- import { Matrix3 } from '../types' ;
1613import { useColors } from './use_colors' ;
1714
1815/**
@@ -45,107 +42,27 @@ interface ResolverSubmenuOption {
4542
4643export type ResolverSubmenuOptionList = ResolverSubmenuOption [ ] | string ;
4744
48- const StyledActionButton = styled ( EuiButton ) `
49- &.euiButton--small {
50- height: fit-content;
51- line-height: 1;
52- padding: 0.25em;
53- font-size: 0.85rem;
54- }
55- ` ;
56-
57- /**
58- * This will be the "host button" that displays the "total number of related events" and opens
59- * the sumbmenu (with counts by category) when clicked.
60- */
61- const SubButton = React . memo (
62- ( {
63- hasMenu,
64- menuIsOpen,
65- action,
66- count,
67- nodeID,
68- } : {
69- hasMenu : boolean ;
70- menuIsOpen ?: boolean ;
71- action : ( evt : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => void ;
72- count ?: number ;
73- nodeID : string ;
74- } ) => {
75- const iconType = menuIsOpen === true ? 'arrowUp' : 'arrowDown' ;
76- return (
77- < StyledActionButton
78- onClick = { action }
79- iconType = { hasMenu ? iconType : 'none' }
80- fill = { false }
81- color = { 'primary' }
82- size = "s"
83- iconSide = "right"
84- tabIndex = { - 1 }
85- data-test-subj = "resolver:submenu:button"
86- data-test-resolver-node-id = { nodeID }
87- id = { nodeID }
88- >
89- { count ? < EuiI18nNumber value = { count } /> : '' } { subMenuAssets . relatedEvents . title }
90- </ StyledActionButton >
91- ) ;
92- }
93- ) ;
94-
9545/**
9646 * A Submenu to be displayed in one of two forms:
9747 * 1) Provided a collection of `optionsWithActions`: it will call `menuAction` then - if and when menuData becomes available - display each item with an optional prefix and call the supplied action for the options when that option is clicked.
9848 * 2) Provided `optionsWithActions` is undefined, it will call the supplied `menuAction` when its host button is clicked.
9949 */
10050const NodeSubMenuComponents = React . memo (
10151 ( {
102- count,
103- buttonBorderColor,
104- menuAction,
10552 className,
106- projectionMatrix,
10753 nodeID,
10854 relatedEventStats,
10955 } : {
11056 className ?: string ;
111- menuAction ?: ( ) => unknown ;
112- buttonBorderColor : ButtonColor ;
11357 // eslint-disable-next-line react/no-unused-prop-types
11458 buttonFill : string ;
115- count ?: number ;
11659 /**
11760 * Receive the projection matrix, so we can see when the camera position changed, so we can force the submenu to reposition itself.
11861 */
119- projectionMatrix : Matrix3 ;
12062 nodeID : string ;
12163 relatedEventStats : ResolverNodeStats | undefined ;
12264 } ) => {
123- // keep a ref to the popover so we can call its reposition method
124- const popoverRef = useRef < EuiPopover > ( null ) ;
125-
126- const [ menuIsOpen , setMenuOpen ] = useState ( false ) ;
127- const handleMenuOpenClick = useCallback (
128- ( clickEvent : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => {
129- // stopping propagation/default to prevent other node animations from triggering
130- clickEvent . preventDefault ( ) ;
131- clickEvent . stopPropagation ( ) ;
132- setMenuOpen ( ! menuIsOpen ) ;
133- } ,
134- [ menuIsOpen ]
135- ) ;
136- const handleMenuActionClick = useCallback (
137- ( clickEvent : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => {
138- // stopping propagation/default to prevent other node animations from triggering
139- clickEvent . preventDefault ( ) ;
140- clickEvent . stopPropagation ( ) ;
141- if ( typeof menuAction === 'function' ) menuAction ( ) ;
142- setMenuOpen ( true ) ;
143- } ,
144- [ menuAction ]
145- ) ;
146-
14765 // The last projection matrix that was used to position the popover
148- const projectionMatrixAtLastRender = useRef < Matrix3 > ( ) ;
14966 const relatedEventCallbacks = useRelatedEventByCategoryNavigation ( {
15067 nodeID,
15168 categories : relatedEventStats ?. events ?. byCategory ,
@@ -164,90 +81,39 @@ const NodeSubMenuComponents = React.memo(
16481 }
16582 } , [ relatedEventStats , relatedEventCallbacks ] ) ;
16683
167- useLayoutEffect ( ( ) => {
168- if (
169- /**
170- * If there is a popover component reference,
171- * and this isn't the first render,
172- * and the projectionMatrix has changed since last render,
173- * then force the popover to reposition itself.
174- */
175- popoverRef . current &&
176- projectionMatrixAtLastRender . current &&
177- projectionMatrixAtLastRender . current !== projectionMatrix
178- ) {
179- popoverRef . current . positionPopoverFixed ( ) ;
180- }
181-
182- // no matter what, keep track of the last project matrix that was used to size the popover
183- projectionMatrixAtLastRender . current = projectionMatrix ;
184- } , [ projectionMatrixAtLastRender , projectionMatrix ] ) ;
18584 const { pillStroke : pillBorderStroke , resolverBackground : pillFill } = useColors ( ) ;
18685 const listStylesFromTheme = useMemo ( ( ) => {
18786 return {
18887 border : `1.5px solid ${ pillBorderStroke } ` ,
18988 backgroundColor : pillFill ,
19089 } ;
19190 } , [ pillBorderStroke , pillFill ] ) ;
192- if ( relatedEventStats === undefined ) {
193- /**
194- * When called with a `menuAction`
195- * Render without dropdown and call the supplied action when host button is clicked
196- */
197- return (
198- < div className = { className } >
199- < EuiButton
200- onClick = { handleMenuActionClick }
201- color = { buttonBorderColor }
202- size = "s"
203- tabIndex = { - 1 }
204- >
205- { subMenuAssets . relatedEvents . title }
206- </ EuiButton >
207- </ div >
208- ) ;
209- }
21091
21192 if ( relatedEventOptions === undefined ) {
21293 return null ;
21394 }
21495
21596 return (
216- < >
217- < SubButton
218- hasMenu = { true }
219- menuIsOpen = { menuIsOpen }
220- action = { handleMenuOpenClick }
221- count = { count }
222- nodeID = { nodeID }
223- />
224- { menuIsOpen ? (
225- < ul
226- className = { `${ className } options` }
227- aria-hidden = { ! menuIsOpen }
228- aria-describedby = { nodeID }
229- >
230- { relatedEventOptions
231- . sort ( ( opta , optb ) => {
232- return opta . optionTitle . localeCompare ( optb . optionTitle ) ;
233- } )
234- . map ( ( opt ) => {
235- return (
236- < li
237- className = "item"
238- data-test-subj = "resolver:map:node-submenu-item"
239- style = { listStylesFromTheme }
240- key = { opt . optionTitle }
241- >
242- < button type = "button" className = "kbn-resetFocusState" onClick = { opt . action } >
243- { opt . prefix } { opt . optionTitle }
244- </ button >
245- </ li >
246- ) ;
247- } ) }
248- </ ul >
249- ) : null }
250- </ >
97+ < ul className = { `${ className } options` } aria-describedby = { nodeID } >
98+ { relatedEventOptions
99+ . sort ( ( opta , optb ) => {
100+ return opta . optionTitle . localeCompare ( optb . optionTitle ) ;
101+ } )
102+ . map ( ( opt ) => {
103+ return (
104+ < li
105+ className = "item"
106+ data-test-subj = "resolver:map:node-submenu-item"
107+ style = { listStylesFromTheme }
108+ key = { opt . optionTitle }
109+ >
110+ < button type = "button" className = "kbn-resetFocusState" onClick = { opt . action } >
111+ { opt . prefix } { opt . optionTitle }
112+ </ button >
113+ </ li >
114+ ) ;
115+ } ) }
116+ </ ul >
251117 ) ;
252118 }
253119) ;
@@ -265,7 +131,7 @@ export const NodeSubMenu = styled(NodeSubMenuComponents)`
265131 flex-flow: row wrap;
266132 background: transparent;
267133 position: absolute;
268- top: 6 .5em;
134+ top: 4 .5em;
269135 contain: content;
270136 width: 12em;
271137 z-index: 2;
@@ -300,17 +166,4 @@ export const NodeSubMenu = styled(NodeSubMenuComponents)`
300166 &.options .item button:active {
301167 transform: scale(0.95);
302168 }
303-
304- & .euiButton {
305- background-color: ${ ( props ) => props . buttonFill } ;
306- border-color: ${ ( props ) => props . buttonBorderColor } ;
307- border-style: solid;
308- border-width: 1px;
309-
310- &:hover,
311- &:active,
312- &:focus {
313- background-color: ${ ( props ) => props . buttonFill } ;
314- }
315- }
316169` ;
0 commit comments