From 797fe9b91785e99fbf8f0e6f489f4fe5ba882515 Mon Sep 17 00:00:00 2001 From: M-ZubairAhmed Date: Tue, 23 Jan 2024 12:18:22 +0000 Subject: [PATCH] [MM-56599] Upgrade to "@floating-ui/react" for advanced_text_editor/formatting_bar component (#25970) --- webapp/channels/package.json | 1 + .../formatting_bar/formatting_bar.tsx | 62 +++++++------------ .../formatting_bar/hooks.tsx | 9 +-- webapp/package-lock.json | 53 +++++++++++++--- .../components/src/focus_trap/index.tsx | 18 ------ webapp/platform/components/src/index.tsx | 2 - 6 files changed, 68 insertions(+), 77 deletions(-) delete mode 100644 webapp/platform/components/src/focus_trap/index.tsx diff --git a/webapp/channels/package.json b/webapp/channels/package.json index fc7f91e38a..f8265d8488 100644 --- a/webapp/channels/package.json +++ b/webapp/channels/package.json @@ -6,6 +6,7 @@ "version": "9.3.0", "private": true, "dependencies": { + "@floating-ui/react": "0.26.6", "@floating-ui/react-dom": "1.0.0", "@floating-ui/react-dom-interactions": "0.10.3", "@giphy/js-fetch-api": "5.1.0", diff --git a/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_bar.tsx b/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_bar.tsx index 2152832ed7..b4ddd7985b 100644 --- a/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_bar.tsx +++ b/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_bar.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {useFloating, offset} from '@floating-ui/react-dom'; +import {useFloating, offset, useClick, useDismiss, useInteractions} from '@floating-ui/react'; import classNames from 'classnames'; import React, {memo, useCallback, useEffect, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; @@ -13,7 +13,7 @@ import {DotsHorizontalIcon} from '@mattermost/compass-icons/components'; import type {ApplyMarkdownOptions} from 'utils/markdown/apply_markdown'; import FormattingIcon, {IconContainer} from './formatting_icon'; -import {useFormattingBarControls, useGetLatest} from './hooks'; +import {useFormattingBarControls} from './hooks'; export const Separator = styled.div` display: block; @@ -147,38 +147,22 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => { const {formatMessage} = useIntl(); const HiddenControlsButtonAriaLabel = formatMessage({id: 'accessibility.button.hidden_controls_button', defaultMessage: 'show hidden formatting options'}); - const {x, y, reference, floating, strategy, update, refs: {reference: buttonRef, floating: floatingRef}} = useFloating({ + const {x, y, strategy, update, context, refs: {setReference, setFloating}} = useFloating({ + open: showHiddenControls, + onOpenChange: setShowHiddenControls, placement: 'top', middleware: [offset({mainAxis: 4})], }); - // this little helper hook always returns the latest refs and does not mess with the popper placement calculation - const getLatest = useGetLatest({ - showHiddenControls, - buttonRef, - floatingRef, - }); + const click = useClick(context); + const {getReferenceProps: getClickReferenceProps, getFloatingProps: getClickFloatingProps} = useInteractions([ + click, + ]); - useEffect(() => { - const handleClickOutside: EventListener = (event) => { - const {floatingRef, buttonRef} = getLatest(); - const target = event.composedPath?.()?.[0] || event.target; - if (target instanceof Node) { - if ( - floatingRef !== null && - buttonRef !== null && - !floatingRef.current?.contains(target) && - !buttonRef.current?.contains(target) - ) { - setShowHiddenControls(false); - } - } - }; - - document.addEventListener('mousedown', handleClickOutside); - - return () => document.removeEventListener('mousedown', handleClickOutside); - }, [getLatest, setShowHiddenControls]); + const dismiss = useDismiss(context); + const {getReferenceProps: getDismissReferenceProps, getFloatingProps: getDismissFloatingProps} = useInteractions([ + dismiss, + ]); useEffect(() => { update?.(); @@ -186,13 +170,6 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => { const hasHiddenControls = wideMode !== 'wide'; - const toggleHiddenControls = useCallback((event?) => { - if (event) { - event.preventDefault(); - } - setShowHiddenControls(!showHiddenControls); - }, [showHiddenControls]); - /** * wrapping this factory in useCallback prevents it from constantly getting a new * function signature as if we would define it directly in the props of @@ -222,9 +199,9 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => { // if hidden controls are currently open close them if (showHiddenControls) { - toggleHiddenControls(); + setShowHiddenControls(true); } - }, [getCurrentSelection, getCurrentMessage, applyMarkdown, showHiddenControls, toggleHiddenControls, disableControls]); + }, [getCurrentSelection, getCurrentMessage, applyMarkdown, showHiddenControls, disableControls]); const leftPosition = wideMode === 'min' ? (x ?? 0) + DEFAULT_MIN_MODE_X_COORD : x ?? 0; @@ -263,10 +240,11 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => { <> { in={showHiddenControls} > {hiddenControls.map((mode) => { return ( diff --git a/webapp/channels/src/components/advanced_text_editor/formatting_bar/hooks.tsx b/webapp/channels/src/components/advanced_text_editor/formatting_bar/hooks.tsx index 448070c2cc..86aca70825 100644 --- a/webapp/channels/src/components/advanced_text_editor/formatting_bar/hooks.tsx +++ b/webapp/channels/src/components/advanced_text_editor/formatting_bar/hooks.tsx @@ -3,18 +3,13 @@ import type {Instance} from '@popperjs/core'; import {debounce} from 'lodash'; -import React, {useCallback, useEffect, useLayoutEffect, useState} from 'react'; +import type React from 'react'; +import {useCallback, useEffect, useLayoutEffect, useState} from 'react'; import type {MarkdownMode} from 'utils/markdown/apply_markdown'; type WideMode = 'wide' | 'normal' | 'narrow' | 'min'; -export function useGetLatest(val: T) { - const ref = React.useRef(val); - ref.current = val; - return React.useCallback(() => ref.current, []); -} - const useResponsiveFormattingBar = (ref: React.RefObject): WideMode => { const [wideMode, setWideMode] = useState('wide'); const handleResize = useCallback(debounce(() => { diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 6fa18d727e..e38f5728a7 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -52,6 +52,7 @@ "name": "mattermost-webapp", "version": "9.3.0", "dependencies": { + "@floating-ui/react": "0.26.6", "@floating-ui/react-dom": "1.0.0", "@floating-ui/react-dom-interactions": "0.10.3", "@giphy/js-fetch-api": "5.1.0", @@ -2362,18 +2363,34 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.5.0", - "license": "MIT", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.3.tgz", + "integrity": "sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q==", "dependencies": { - "@floating-ui/utils": "^0.1.3" + "@floating-ui/utils": "^0.2.0" } }, "node_modules/@floating-ui/dom": { - "version": "1.5.3", - "license": "MIT", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.4.tgz", + "integrity": "sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ==", "dependencies": { - "@floating-ui/core": "^1.4.2", - "@floating-ui/utils": "^0.1.3" + "@floating-ui/core": "^1.5.3", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.6.tgz", + "integrity": "sha512-FFDAuSlRwb8CY4/VvYio/wwk/0339B257yRpKwNOjcHWNYL/fgjl1KUvT3K6ZZ4WDbBWYc7Km4ITMuPZrS8omg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.6", + "@floating-ui/utils": "^0.2.1", + "tabbable": "^6.0.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, "node_modules/@floating-ui/react-dom": { @@ -2399,9 +2416,22 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@floating-ui/react/node_modules/@floating-ui/react-dom": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.6.tgz", + "integrity": "sha512-IB8aCRFxr8nFkdYZgH+Otd9EVQPJoynxeFRGTB8voPoZMRWo8XjYuCRgpI1btvuKY69XMiLnW+ym7zoBHM90Rw==", + "dependencies": { + "@floating-ui/dom": "^1.5.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/utils": { - "version": "0.1.6", - "license": "MIT" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.15.0", @@ -22320,6 +22350,11 @@ "dev": true, "license": "MIT" }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/table": { "version": "6.8.1", "dev": true, diff --git a/webapp/platform/components/src/focus_trap/index.tsx b/webapp/platform/components/src/focus_trap/index.tsx deleted file mode 100644 index 7d913f2417..0000000000 --- a/webapp/platform/components/src/focus_trap/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import MuiFocusTrap, {FocusTrapProps as MuiFocusTrapProps} from '@mui/base/FocusTrap'; - -export interface Props { - active: MuiFocusTrapProps['open']; - children: MuiFocusTrapProps['children']; -} - -export const FocusTrap = ({active, children}: Props) => { - return ( - - {children} - - ); -}; diff --git a/webapp/platform/components/src/index.tsx b/webapp/platform/components/src/index.tsx index c4b5c4f261..964b4bde50 100644 --- a/webapp/platform/components/src/index.tsx +++ b/webapp/platform/components/src/index.tsx @@ -4,7 +4,6 @@ // type export type {Props as GenericModalProps} from './generic_modal/generic_modal'; export type {CircleSkeletonLoaderProps, RectangleSkeletonLoaderProps} from './skeleton_loader'; -export type {Props as FocusTrapProps} from './focus_trap'; export type {Props as PunchOutCoordsHeightAndWidth} from './common/hooks/useMeasurePunchouts'; // components @@ -14,7 +13,6 @@ export {CircleSkeletonLoader, RectangleSkeletonLoader} from './skeleton_loader'; export {TourTip} from './tour_tip/tour_tip'; export {TourTipBackdrop} from './tour_tip/tour_tip_backdrop'; export {PulsatingDot} from './pulsating_dot'; -export {FocusTrap} from './focus_trap'; // hooks export {useMeasurePunchouts} from './common/hooks/useMeasurePunchouts';