mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-56599] Upgrade to "@floating-ui/react" for advanced_text_editor/formatting_bar component (#25970)
This commit is contained in:
parent
62064e3bf7
commit
797fe9b917
@ -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",
|
||||
|
@ -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<HTMLButtonElement>({
|
||||
const {x, y, strategy, update, context, refs: {setReference, setFloating}} = useFloating<HTMLButtonElement>({
|
||||
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 => {
|
||||
<>
|
||||
<IconContainer
|
||||
id={'HiddenControlsButton' + location}
|
||||
ref={reference}
|
||||
ref={setReference}
|
||||
className={classNames({active: showHiddenControls})}
|
||||
onClick={toggleHiddenControls}
|
||||
aria-label={HiddenControlsButtonAriaLabel}
|
||||
{...getClickReferenceProps()}
|
||||
{...getDismissReferenceProps()}
|
||||
>
|
||||
<DotsHorizontalIcon
|
||||
color={'currentColor'}
|
||||
@ -282,8 +260,10 @@ const FormattingBar = (props: FormattingBarProps): JSX.Element => {
|
||||
in={showHiddenControls}
|
||||
>
|
||||
<HiddenControlsContainer
|
||||
ref={floating}
|
||||
ref={setFloating}
|
||||
style={hiddenControlsContainerStyles}
|
||||
{...getClickFloatingProps()}
|
||||
{...getDismissFloatingProps()}
|
||||
>
|
||||
{hiddenControls.map((mode) => {
|
||||
return (
|
||||
|
@ -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<T>(val: T) {
|
||||
const ref = React.useRef<T>(val);
|
||||
ref.current = val;
|
||||
return React.useCallback(() => ref.current, []);
|
||||
}
|
||||
|
||||
const useResponsiveFormattingBar = (ref: React.RefObject<HTMLDivElement>): WideMode => {
|
||||
const [wideMode, setWideMode] = useState<WideMode>('wide');
|
||||
const handleResize = useCallback(debounce(() => {
|
||||
|
53
webapp/package-lock.json
generated
53
webapp/package-lock.json
generated
@ -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,
|
||||
|
@ -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 (
|
||||
<MuiFocusTrap open={active}>
|
||||
{children}
|
||||
</MuiFocusTrap>
|
||||
);
|
||||
};
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user