From 6732332aab2b45093238799e60494782b7ee31aa Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 11 Mar 2024 11:30:16 -0700 Subject: [PATCH 1/5] [EuiToast] Prevent text overflow on all toast text, not just the body + remove now-unnecessary body styles --- .../__snapshots__/global_toast_list.test.tsx.snap | 8 ++++---- .../toast/__snapshots__/toast.test.tsx.snap | 2 +- src/components/toast/toast.styles.ts | 11 +++-------- src/components/toast/toast.tsx | 13 ++----------- 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/components/toast/__snapshots__/global_toast_list.test.tsx.snap b/src/components/toast/__snapshots__/global_toast_list.test.tsx.snap index b9b1f969a8e..d35e4ff73ed 100644 --- a/src/components/toast/__snapshots__/global_toast_list.test.tsx.snap +++ b/src/components/toast/__snapshots__/global_toast_list.test.tsx.snap @@ -57,7 +57,7 @@ exports[`EuiGlobalToastList props side can be changed to left 1`] = ` />
a @@ -104,7 +104,7 @@ exports[`EuiGlobalToastList props side can be changed to left 1`] = ` />
b @@ -160,7 +160,7 @@ exports[`EuiGlobalToastList props toasts is rendered 1`] = ` />
a @@ -207,7 +207,7 @@ exports[`EuiGlobalToastList props toasts is rendered 1`] = ` />
b diff --git a/src/components/toast/__snapshots__/toast.test.tsx.snap b/src/components/toast/__snapshots__/toast.test.tsx.snap index 7e0fec703f4..6c530cda117 100644 --- a/src/components/toast/__snapshots__/toast.test.tsx.snap +++ b/src/components/toast/__snapshots__/toast.test.tsx.snap @@ -173,7 +173,7 @@ exports[`EuiToast is rendered 1`] = `

diff --git a/src/components/toast/toast.styles.ts b/src/components/toast/toast.styles.ts index ccaf3413f67..7ad7279b6af 100644 --- a/src/components/toast/toast.styles.ts +++ b/src/components/toast/toast.styles.ts @@ -7,7 +7,7 @@ */ import { css } from '@emotion/react'; -import { logicalCSS } from '../../global_styling'; +import { euiTextBreakWord, logicalCSS } from '../../global_styling'; import { UseEuiTheme } from '../../services'; import { euiShadowLarge } from '../../themes/amsterdam'; import { euiTitle } from '../title/title.styles'; @@ -27,6 +27,8 @@ export const euiToastStyles = (euiThemeContext: UseEuiTheme) => { background-color: ${euiTheme.colors.emptyShade}; ${logicalCSS('width', '100%')} + ${euiTextBreakWord()} /* Prevent long lines from overflowing */ + &:hover, &:focus { [class*='euiToast__closeButton'] { @@ -90,10 +92,3 @@ export const euiToastHeaderStyles = (euiThemeContext: UseEuiTheme) => { `, }; }; - -export const euiToastBodyStyles = () => ({ - // Base - euiToastBody: css` - word-wrap: break-word; /* Prevent long lines from overflowing */ - `, -}); diff --git a/src/components/toast/toast.tsx b/src/components/toast/toast.tsx index 09935b0f338..a1e7d164b30 100644 --- a/src/components/toast/toast.tsx +++ b/src/components/toast/toast.tsx @@ -22,11 +22,7 @@ import { EuiI18n } from '../i18n'; import { IconType, EuiIcon } from '../icon'; import { EuiText } from '../text'; -import { - euiToastStyles, - euiToastBodyStyles, - euiToastHeaderStyles, -} from './toast.styles'; +import { euiToastStyles, euiToastHeaderStyles } from './toast.styles'; export const COLORS = ['primary', 'success', 'warning', 'danger'] as const; @@ -53,7 +49,6 @@ export const EuiToast: FunctionComponent = ({ const euiTheme = useEuiTheme(); const baseStyles = euiToastStyles(euiTheme); const baseCss = [baseStyles.euiToast, color && baseStyles[color]]; - const bodyStyles = euiToastBodyStyles(); const headerStyles = euiToastHeaderStyles(euiTheme); const headerCss = [ headerStyles.euiToastHeader, @@ -99,11 +94,7 @@ export const EuiToast: FunctionComponent = ({ if (children) { optionalBody = ( - + {children} ); From 7cb368daaa9ec8d53e60f6c76b118653a11e13f9 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 11 Mar 2024 11:32:42 -0700 Subject: [PATCH 2/5] [perf] Memoize Emotion styles --- src/components/toast/global_toast_list.tsx | 5 ++--- src/components/toast/toast.tsx | 7 +++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/toast/global_toast_list.tsx b/src/components/toast/global_toast_list.tsx index d623badfd0a..979fe2f0f45 100644 --- a/src/components/toast/global_toast_list.tsx +++ b/src/components/toast/global_toast_list.tsx @@ -18,7 +18,7 @@ import React, { import classNames from 'classnames'; import { CommonProps, keysOf } from '../common'; -import { useEuiTheme } from '../../services'; +import { useEuiMemoizedStyles } from '../../services'; import { Timer } from '../../services/time'; import { EuiGlobalToastListItem } from './global_toast_list_item'; import { EuiToast, EuiToastProps } from './toast'; @@ -107,8 +107,7 @@ export const EuiGlobalToastList: FunctionComponent = ({ const listElement = useRef(null); - const euiTheme = useEuiTheme(); - const styles = euiGlobalToastListStyles(euiTheme); + const styles = useEuiMemoizedStyles(euiGlobalToastListStyles); const cssStyles = [styles.euiGlobalToastList, styles[side]]; const startScrollingToBottom = () => { diff --git a/src/components/toast/toast.tsx b/src/components/toast/toast.tsx index a1e7d164b30..3a61295f0e5 100644 --- a/src/components/toast/toast.tsx +++ b/src/components/toast/toast.tsx @@ -14,7 +14,7 @@ import React, { } from 'react'; import classNames from 'classnames'; -import { useEuiTheme } from '../../services'; +import { useEuiMemoizedStyles } from '../../services'; import { CommonProps } from '../common'; import { EuiScreenReaderOnly } from '../accessibility'; import { EuiButtonIcon } from '../button'; @@ -46,10 +46,9 @@ export const EuiToast: FunctionComponent = ({ className, ...rest }) => { - const euiTheme = useEuiTheme(); - const baseStyles = euiToastStyles(euiTheme); + const baseStyles = useEuiMemoizedStyles(euiToastStyles); const baseCss = [baseStyles.euiToast, color && baseStyles[color]]; - const headerStyles = euiToastHeaderStyles(euiTheme); + const headerStyles = useEuiMemoizedStyles(euiToastHeaderStyles); const headerCss = [ headerStyles.euiToastHeader, children && headerStyles.withBody, From 46dbca34e24cd32d3edc0746d8ca529bcdf5bb62 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 11 Mar 2024 11:58:58 -0700 Subject: [PATCH 3/5] [EuiToast][react perf] Remove conditional let JSX - creates a new React component on every rerender - use inline JSX --- src/components/toast/toast.tsx | 86 ++++++++++++++-------------------- 1 file changed, 34 insertions(+), 52 deletions(-) diff --git a/src/components/toast/toast.tsx b/src/components/toast/toast.tsx index 3a61295f0e5..e8f555aa74a 100644 --- a/src/components/toast/toast.tsx +++ b/src/components/toast/toast.tsx @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -import React, { - FunctionComponent, - HTMLAttributes, - ReactElement, - ReactNode, -} from 'react'; +import React, { FunctionComponent, HTMLAttributes, ReactNode } from 'react'; import classNames from 'classnames'; import { useEuiMemoizedStyles } from '../../services'; @@ -56,51 +51,9 @@ export const EuiToast: FunctionComponent = ({ const classes = classNames('euiToast', className); - let headerIcon: ReactElement; - - if (iconType) { - headerIcon = ( -

+ {/* Screen reader announcement */}

= ({

+ {/* Header */} {(notification: string) => (
= ({ aria-label={notification} data-test-subj="euiToastHeader" > - {headerIcon} + {iconType && ( +
); }; From c8fab09dcb77ca1b206e68d6cd863d55f1ee0fa0 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 11 Mar 2024 12:00:42 -0700 Subject: [PATCH 4/5] [EuiGlobalToastList][perf] Memoize callbacks + toast iteration + split out clear button from rendered toasts map --- src/components/toast/global_toast_list.tsx | 173 +++++++++++---------- 1 file changed, 91 insertions(+), 82 deletions(-) diff --git a/src/components/toast/global_toast_list.tsx b/src/components/toast/global_toast_list.tsx index 979fe2f0f45..6238a14c42a 100644 --- a/src/components/toast/global_toast_list.tsx +++ b/src/components/toast/global_toast_list.tsx @@ -12,6 +12,7 @@ import React, { ReactNode, useCallback, useEffect, + useMemo, useRef, useState, } from 'react'; @@ -110,7 +111,7 @@ export const EuiGlobalToastList: FunctionComponent = ({ const styles = useEuiMemoizedStyles(euiGlobalToastListStyles); const cssStyles = [styles.euiGlobalToastList, styles[side]]; - const startScrollingToBottom = () => { + const startScrollingToBottom = useCallback(() => { isScrollingToBottom.current = true; const scrollToBottom = () => { @@ -142,9 +143,9 @@ export const EuiGlobalToastList: FunctionComponent = ({ startScrollingAnimationFrame.current = window.requestAnimationFrame(scrollToBottom); - }; + }, []); - const onMouseEnter = () => { + const onMouseEnter = useCallback(() => { // Stop scrolling to bottom if we're in mid-scroll, because the user wants to interact with // the list. isScrollingToBottom.current = false; @@ -157,9 +158,9 @@ export const EuiGlobalToastList: FunctionComponent = ({ timer.pause(); } } - }; + }, []); - const onMouseLeave = () => { + const onMouseLeave = useCallback(() => { isUserInteracting.current = false; for (const toastId in toastIdToTimerMap.current) { if (toastIdToTimerMap.current.hasOwnProperty(toastId)) { @@ -167,9 +168,9 @@ export const EuiGlobalToastList: FunctionComponent = ({ timer.resume(); } } - }; + }, []); - const onScroll = () => { + const onScroll = useCallback(() => { // Given that this method also gets invoked by the synthetic scroll that happens when a new toast gets added, // we want to evaluate if the scroll bottom has been reached only when the user is interacting with the toast, // this way we always retain the scroll position the user has set despite adding in new toasts. @@ -179,7 +180,7 @@ export const EuiGlobalToastList: FunctionComponent = ({ listElement.current.scrollHeight - listElement.current.scrollTop === listElement.current.clientHeight; } - }; + }, []); const dismissToast = useCallback((toast: Toast) => { // Remove the toast after it's done fading out. @@ -214,35 +215,28 @@ export const EuiGlobalToastList: FunctionComponent = ({ }); }, [scheduleToastForDismissal, toasts]); - const addListeners = () => { - if (listElement.current) { - listElement.current.addEventListener('scroll', onScroll); - listElement.current.addEventListener('mouseenter', onMouseEnter); - listElement.current.addEventListener('mouseleave', onMouseLeave); - } - }; - - const removeListeners = () => { - if (listElement.current) { - listElement.current.removeEventListener('scroll', onScroll); - listElement.current.removeEventListener('mouseenter', onMouseEnter); - listElement.current.removeEventListener('mouseleave', onMouseLeave); - } - }; - // componentDidMount useEffect(() => { - addListeners(); + const listenerEl = listElement.current; + if (listenerEl) { + listenerEl.addEventListener('scroll', onScroll); + listenerEl.addEventListener('mouseenter', onMouseEnter); + listenerEl.addEventListener('mouseleave', onMouseLeave); + } // componentWillUnmount return () => { + if (listenerEl) { + listenerEl.removeEventListener('scroll', onScroll); + listenerEl.removeEventListener('mouseenter', onMouseEnter); + listenerEl.removeEventListener('mouseleave', onMouseLeave); + } if (isScrollingAnimationFrame.current !== 0) { window.cancelAnimationFrame(isScrollingAnimationFrame.current); } if (startScrollingAnimationFrame.current !== 0) { window.cancelAnimationFrame(startScrollingAnimationFrame.current); } - removeListeners(); dismissTimeoutIds.current.forEach(clearTimeout); // eslint-disable-line react-hooks/exhaustive-deps for (const toastId in toastIdToTimerMap.current) { if (toastIdToTimerMap.current.hasOwnProperty(toastId)) { @@ -251,7 +245,7 @@ export const EuiGlobalToastList: FunctionComponent = ({ } } }; - }, []); // eslint-disable-line react-hooks/exhaustive-deps + }, [onMouseEnter, onMouseLeave, onScroll]); // componentDidUpdate useEffect(() => { @@ -267,7 +261,7 @@ export const EuiGlobalToastList: FunctionComponent = ({ } } prevToasts.current = toasts; - }, [toasts, scheduleAllToastsForDismissal]); + }, [toasts, scheduleAllToastsForDismissal, startScrollingToBottom]); // Toast dismissal side effect // Ensure the callback has correct state by not enclosing it in `setTimeout` @@ -293,62 +287,76 @@ export const EuiGlobalToastList: FunctionComponent = ({ } }, [toastToDismiss, dismissToastProp]); - const renderedToasts = toasts.map((toast) => { - const { text, toastLifeTimeMs, ...rest } = toast; - const onClose = () => dismissToast(toast); - - return ( - - - {text} - - - ); - }); - - if (showClearAllButtonAt && toasts.length >= showClearAllButtonAt) { - const dismissAllToasts = () => { - toasts.forEach((toast) => dismissToastProp(toast)); - onClearAllToasts?.(); - }; - - renderedToasts.push( - - {([ - clearAllToastsButtonAriaLabel, - clearAllToastsButtonDisplayText, - ]: string[]) => ( - - + toasts.map((toast) => { + const { text, toastLifeTimeMs, ...rest } = toast; + const onClose = () => dismissToast(toast); + + return ( + + - {clearAllToastsButtonDisplayText} - + {text} + - )} - - ); - } + ); + }), + [toasts, toastIdToDismissedMap, dismissToast, onMouseEnter, onMouseLeave] + ); + + const clearAllButton = useMemo(() => { + if ( + toasts.length && + showClearAllButtonAt && + toasts.length >= showClearAllButtonAt + ) { + return ( + + {([ + clearAllToastsButtonAriaLabel, + clearAllToastsButtonDisplayText, + ]: string[]) => ( + + { + toasts.forEach((toast) => dismissToastProp(toast)); + onClearAllToasts?.(); + }} + css={styles.euiGlobalToastListDismissButton} + aria-label={clearAllToastsButtonAriaLabel} + data-test-subj="euiClearAllToastsButton" + > + {clearAllToastsButtonDisplayText} + + + )} + + ); + } + }, [ + showClearAllButtonAt, + onClearAllToasts, + toasts, + dismissToastProp, + styles, + ]); const classes = classNames('euiGlobalToastList', className); @@ -362,6 +370,7 @@ export const EuiGlobalToastList: FunctionComponent = ({ {...rest} > {renderedToasts} + {clearAllButton}
); }; From f0de934ee41d5a99b39cd57e1711b48edb4e3b0b Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Mon, 11 Mar 2024 12:12:43 -0700 Subject: [PATCH 5/5] changelog --- changelogs/upcoming/7568.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelogs/upcoming/7568.md diff --git a/changelogs/upcoming/7568.md b/changelogs/upcoming/7568.md new file mode 100644 index 00000000000..a2f8bbb4a26 --- /dev/null +++ b/changelogs/upcoming/7568.md @@ -0,0 +1,3 @@ +**Bug fixes** + +- Fixed `EuiToast` title text to wrap instead of overflowing out of the container