From c67a5b10e6e61b17c161fda3cc2a4116cc017b2c Mon Sep 17 00:00:00 2001 From: M-ZubairAhmed Date: Thu, 22 Aug 2024 14:12:25 +0000 Subject: [PATCH] [MM-60213] Migrate tooltips of "components/toast" to WithTooltip (#28001) --- .../team/details/team_profile.tsx | 1 - .../file_preview_modal_main_actions.tsx | 13 +- .../post_view/reaction/reaction.tsx | 6 - .../toast/__snapshots__/toast.test.tsx.snap | 424 +++++++++--------- .../src/components/toast/toast.test.tsx | 33 +- .../channels/src/components/toast/toast.tsx | 168 +++---- .../toast_wrapper/toast_wrapper.tsx | 21 +- .../widgets/simple_tooltip/index.ts | 1 - .../widgets/simple_tooltip/simple_tooltip.tsx | 15 +- .../with_tooltip/create_tooltip.tsx | 1 - .../src/components/with_tooltip/index.tsx | 5 - .../src/types/external/react-bootstrap.d.ts | 21 - 12 files changed, 333 insertions(+), 376 deletions(-) delete mode 100644 webapp/channels/src/types/external/react-bootstrap.d.ts diff --git a/webapp/channels/src/components/admin_console/team_channel_settings/team/details/team_profile.tsx b/webapp/channels/src/components/admin_console/team_channel_settings/team/details/team_profile.tsx index 9bc47a2435..7ed691d2e9 100644 --- a/webapp/channels/src/components/admin_console/team_channel_settings/team/details/team_profile.tsx +++ b/webapp/channels/src/components/admin_console/team_channel_settings/team/details/team_profile.tsx @@ -71,7 +71,6 @@ export function TeamProfile({team, isArchived, onToggleArchive, isDisabled, save hint={defineMessage({id: 'workspace_limits.teams_limit_reached.tool_tip', defaultMessage: 'You\'ve reached the team limit for your current plan. Consider upgrading to unarchive this team or archive your other teams'})} placement='bottom' > - {/* OverlayTrigger doesn't play nicely with `disabled` buttons, because the :hover events don't fire. This is a workaround to ensure the popover appears see: https://github.com/react-bootstrap/react-bootstrap/issues/1588*/}
diff --git a/webapp/channels/src/components/file_preview_modal/file_preview_modal_main_actions/file_preview_modal_main_actions.tsx b/webapp/channels/src/components/file_preview_modal/file_preview_modal_main_actions/file_preview_modal_main_actions.tsx index 4794972a9f..20072704ea 100644 --- a/webapp/channels/src/components/file_preview_modal/file_preview_modal_main_actions/file_preview_modal_main_actions.tsx +++ b/webapp/channels/src/components/file_preview_modal/file_preview_modal_main_actions/file_preview_modal_main_actions.tsx @@ -24,6 +24,8 @@ import type {LinkInfo} from '../types'; import './file_preview_modal_main_actions.scss'; +const COPIED_TOOLTIP_DURATION = 2000; + interface Props { usedInside?: 'Header' | 'Footer'; showOnlyClose?: boolean; @@ -52,6 +54,15 @@ const FilePreviewModalMainActions: React.FC = (props: Props) => { dispatch(getFilePublicLink(props.fileInfo.id)); } }, [props.fileInfo, props.enablePublicLink]); + + useEffect(() => { + if (publicLinkCopied) { + setTimeout(() => { + setPublicLinkCopied(false); + }, COPIED_TOOLTIP_DURATION); + } + }, [publicLinkCopied]); + const copyPublicLink = () => { copyToClipboard(selectedFilePublicLink ?? ''); setPublicLinkCopied(true); @@ -96,8 +107,6 @@ const FilePreviewModalMainActions: React.FC = (props: Props) => { key='filePreviewPublicLink' placement={tooltipPlacement} title={publicTooltipMessage} - shouldUpdatePosition={true} - onExit={() => setPublicLinkCopied(false)} > +
- - Jump to recents +
+ + + + + + Jump to recents +
+ + child +
- - child - -
- } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - >
- + > + + + +
-
+
`; exports[`components/Toast should match snapshot for showing toast 1`] = ` -
+
- - Jump to recents -
- - child - -
- - -
- -
- - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > -
- + + + + + + Jump to recents +
+ + child +
- +
+ + + + + +
+
`; exports[`components/Toast should match snapshot for toast width less than 780px 1`] = ` -
+
- - Jump to recents -
- - child - -
- - -
- -
- - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > -
- + + + + + + Jump to recents +
+ + child +
- +
+ + + + + +
+
`; exports[`components/Toast should match snapshot to have extraClasses 1`] = ` -
+
- - Jump to recents -
- - child - -
- - -
- -
- - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > -
- + + + + + + Jump to recents +
+ + child +
- +
+ + + + + +
+
`; exports[`components/Toast should match snapshot to not have actions 1`] = ` -
+
- - child - -
- } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } + class="toast toast__visible" >
- + + child +
-
+
+ + + + + +
+
`; diff --git a/webapp/channels/src/components/toast/toast.test.tsx b/webapp/channels/src/components/toast/toast.test.tsx index 26d44f2ed3..aaa548f0d2 100644 --- a/webapp/channels/src/components/toast/toast.test.tsx +++ b/webapp/channels/src/components/toast/toast.test.tsx @@ -1,11 +1,10 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {shallow} from 'enzyme'; import React from 'react'; +import {FormattedMessage} from 'react-intl'; import {renderWithContext, screen} from 'tests/react_testing_utils'; -import * as Utils from 'utils/utils'; import Toast from './toast'; import type {Props} from './toast'; @@ -15,30 +14,34 @@ describe('components/Toast', () => { onClick: jest.fn(), show: true, showActions: true, - onClickMessage: Utils.localizeMessage('postlist.toast.scrollToBottom', 'Jump to recents'), + onClickMessage: ( + + ), width: 1000, }; test('should match snapshot for showing toast', () => { - const wrapper = shallow({'child'}); - expect(wrapper).toMatchSnapshot(); + const {container} = renderWithContext({'child'}); + expect(container).toMatchSnapshot(); + expect(screen.getByTestId('dismissToast')).toBeInTheDocument(); }); test('should match snapshot for hiding toast', () => { - const wrapper = shallow({'child'}); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('.toast__visible').length).toBe(0); + const {container} = renderWithContext({'child'}); + expect(container).toMatchSnapshot(); }); test('should match snapshot for toast width less than 780px', () => { - const wrapper = shallow({'child'}); - expect(wrapper).toMatchSnapshot(); + const {container} = renderWithContext({'child'}); + expect(container).toMatchSnapshot(); }); test('should match snapshot to not have actions', () => { - const wrapper = shallow({'child'}); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('.toast__pointer').length).toBe(0); + const {container} = renderWithContext({'child'}); + expect(container).toMatchSnapshot(); }); test('should dismiss', () => { @@ -56,7 +59,7 @@ describe('components/Toast', () => { }); test('should match snapshot to have extraClasses', () => { - const wrapper = shallow({'child'}); - expect(wrapper).toMatchSnapshot(); + const {container} = renderWithContext({'child'}); + expect(container).toMatchSnapshot(); }); }); diff --git a/webapp/channels/src/components/toast/toast.tsx b/webapp/channels/src/components/toast/toast.tsx index 9ce0011cc3..efe4f64476 100644 --- a/webapp/channels/src/components/toast/toast.tsx +++ b/webapp/channels/src/components/toast/toast.tsx @@ -1,16 +1,15 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import classNames from 'classnames'; import React from 'react'; import type {ReactNode, MouseEventHandler} from 'react'; -import type {OverlayTriggerProps} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; -import OverlayTrigger from 'components/overlay_trigger'; -import Tooltip from 'components/tooltip'; import CloseIcon from 'components/widgets/icons/close_icon'; import UnreadAboveIcon from 'components/widgets/icons/unread_above_icon'; import UnreadBelowIcon from 'components/widgets/icons/unread_below_icon'; +import WithTooltip from 'components/with_tooltip'; import Constants from 'utils/constants'; @@ -18,111 +17,88 @@ import './toast.scss'; export type Props = { onClick?: MouseEventHandler; - onClickMessage?: string; + onClickMessage?: ReactNode; onDismiss?: () => void; children?: ReactNode; show: boolean; showActions?: boolean; //used for showing jump actions width: number; extraClasses?: string; - overlayPlacement?: OverlayTriggerProps['placement']; + overlayPlacement?: string; jumpDirection?: 'up' | 'down'; -} +}; -export default class Toast extends React.PureComponent { - private mounted!: boolean; - - static defaultProps = { - overlayPlacement: 'bottom', - jumpDirection: 'down', - }; - - componentDidMount() { - this.mounted = true; +export default function Toast({ + onClick, + onClickMessage, + onDismiss, + children, + show, + showActions, + width, + extraClasses = '', + overlayPlacement = 'bottom', + jumpDirection = 'down', +}: Props) { + function handleDismiss() { + if (typeof onDismiss === 'function') { + onDismiss(); + } } - componentWillUnmount() { - this.mounted = false; - } + const toastClass = classNames('toast', { + toast__visible: show, + [extraClasses]: extraClasses.length > 0, + }); - handleDismiss = () => { - if (typeof this.props.onDismiss == 'function') { - this.props.onDismiss(); - } - }; + const toastActionClass = classNames('toast__message', { + toast__pointer: showActions, + }); - render() { - let toastClass = 'toast'; - const {show, extraClasses, showActions, width, overlayPlacement, jumpDirection} = this.props; - - if (extraClasses) { - toastClass += ` ${extraClasses}`; - } - - if (show) { - toastClass += ' toast__visible'; - } - - let toastActionClass = 'toast__message'; - if (showActions) { - toastActionClass += ' toast__pointer'; - } - - const jumpSection = () => { - return ( -
- {jumpDirection === 'down' ? : } - {width > Constants.MOBILE_SCREEN_WIDTH && this.props.onClickMessage} -
- ); - }; - - let closeTooltip = (
); - if (showActions && show) { - closeTooltip = ( - - -
- + return ( +
+
+ {showActions && ( +
+ {jumpDirection === 'down' ? () : ()} + {width > Constants.MOBILE_SCREEN_WIDTH && onClickMessage}
- - ); - } - - return ( -
-
- {showActions && jumpSection()} - {this.props.children} -
- -
- -
-
+ )} + {children}
- ); - } + + +
+ +
+ + } + disabled={!showActions || !show} + > +
+ +
+
+
+ ); } diff --git a/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx b/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx index a4e9d07d4b..72c8102a06 100644 --- a/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx +++ b/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx @@ -393,7 +393,12 @@ export class ToastWrapperClass extends React.PureComponent { width, onDismiss: this.hideUnreadToast, onClick: this.scrollToLatestMessages, - onClickMessage: localizeMessage('postlist.toast.scrollToBottom', 'Jump to recents'), + onClickMessage: ( + + ), showActions: !atLatestPost || (atLatestPost && (atBottom === false)), }; @@ -410,7 +415,12 @@ export class ToastWrapperClass extends React.PureComponent { width, onDismiss: this.hideUnreadWithBottomStartToast, onClick: this.scrollToUnreadMessages, - onClickMessage: localizeMessage('postlist.toast.scrollToUnread', 'Jump to unreads'), + onClickMessage: ( + + ), showActions: true, jumpDirection: 'up' as const, }; @@ -427,7 +437,12 @@ export class ToastWrapperClass extends React.PureComponent { const showNewMessagesToastOverrides = { onDismiss: this.hideNewMessagesToast, onClick: this.scrollToNewMessage, - onClickMessage: localizeMessage('postlist.toast.scrollToLatest', 'Jump to new messages'), + onClickMessage: ( + + ), }; return ( diff --git a/webapp/channels/src/components/widgets/simple_tooltip/index.ts b/webapp/channels/src/components/widgets/simple_tooltip/index.ts index 763cc9068d..2557ec70ca 100644 --- a/webapp/channels/src/components/widgets/simple_tooltip/index.ts +++ b/webapp/channels/src/components/widgets/simple_tooltip/index.ts @@ -3,5 +3,4 @@ export { default, - useSynchronizedImmediate, } from './simple_tooltip'; diff --git a/webapp/channels/src/components/widgets/simple_tooltip/simple_tooltip.tsx b/webapp/channels/src/components/widgets/simple_tooltip/simple_tooltip.tsx index e5fc59b57d..562e18193a 100644 --- a/webapp/channels/src/components/widgets/simple_tooltip/simple_tooltip.tsx +++ b/webapp/channels/src/components/widgets/simple_tooltip/simple_tooltip.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useState, useMemo} from 'react'; +import React from 'react'; import type {ReactNode, ComponentProps, CSSProperties} from 'react'; import OverlayTrigger from 'components/overlay_trigger'; @@ -48,16 +48,3 @@ const SimpleTooltip = ({ }; export default SimpleTooltip; - -export function useSynchronizedImmediate(): [Partial>, (isImmediate: boolean) => void] { - const [isImmediate, setImmediate] = useState(false); - - return [ - useMemo((): Partial> => ({ - onEntered: () => setImmediate(true), - animation: !isImmediate, - delayShow: isImmediate ? 0 : undefined, - }), [isImmediate, setImmediate]), - setImmediate, - ]; -} diff --git a/webapp/channels/src/components/with_tooltip/create_tooltip.tsx b/webapp/channels/src/components/with_tooltip/create_tooltip.tsx index f3817b1218..7446f95b45 100644 --- a/webapp/channels/src/components/with_tooltip/create_tooltip.tsx +++ b/webapp/channels/src/components/with_tooltip/create_tooltip.tsx @@ -20,7 +20,6 @@ export type CommonTooltipProps = { shortcut?: ShortcutDefinition; emoji?: string; emojiStyle?: EmojiStyle; - shouldUpdatePosition?: boolean; } export function createTooltip(commonTooltipProps: CommonTooltipProps) { diff --git a/webapp/channels/src/components/with_tooltip/index.tsx b/webapp/channels/src/components/with_tooltip/index.tsx index 2ee215ac6d..84a603ea9c 100644 --- a/webapp/channels/src/components/with_tooltip/index.tsx +++ b/webapp/channels/src/components/with_tooltip/index.tsx @@ -18,7 +18,6 @@ type WithTooltipProps = { placement: OverlayTriggerProps['placement']; onShow?: () => void; delayHide?: number; - onExit?: () => void; disabled?: boolean; } & CommonTooltipProps; const WithTooltip = ({ @@ -32,8 +31,6 @@ const WithTooltip = ({ onShow, delayHide, children, - onExit, - shouldUpdatePosition, disabled = false, }: WithTooltipProps) => { const ThisTooltip = useMemo(() => createTooltip({ @@ -52,8 +49,6 @@ const WithTooltip = ({ placement={placement} onEnter={onShow} delayHide={delayHide} - onExit={onExit} - shouldUpdatePosition={shouldUpdatePosition} disabled={disabled} > {children} diff --git a/webapp/channels/src/types/external/react-bootstrap.d.ts b/webapp/channels/src/types/external/react-bootstrap.d.ts deleted file mode 100644 index 310768cd79..0000000000 --- a/webapp/channels/src/types/external/react-bootstrap.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// Here we are extending the Interface defined by @types/react-bootstrap to include out own -// additional types, this soon will be not needed when we upgrade React-Bootstrap version to latest -// Until that happens this is the fix - -import * as React from 'react'; -import type {OverlayTriggerProps} from 'react-bootstrap'; - -export interface AdditionalOverlayTriggerProps extends React.ComponentPropsWithRef { - - className?: string; - overlay: any; -} - -declare class OverlayTrigger extends React.Component {} - -declare module 'react-bootstrap' { - export {OverlayTrigger}; -}