mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: remove react-popper-tooltip in favour of @floating-ui/react (#79465)
* use floating-ui instead of react-popper-tooltip in Tooltip * remove useTheme2 usage * remove escape handling logic in favour of useDismiss * don't need this useEffect anymore * convert Toggletip to use floating-ui * use explicit version * convert OperationInfoButton to use Toggletip * convert nestedFolderPicker to use floating-ui * convert Dropdown to use floating-ui and remove react-popper-tooltip * fix Modal/Tooltip tests * revert to old toggletip behaviour * revert OperationInfoButton to not use Toggletip * add mock for requestAnimationFrame * remove requestAnimationFrame mock * remove fakeTimers where they're not used * use floating-ui in ButtonSelect * Fix filters unit tests * only attach description if label is different * use 'fixed' strategy for Toggletip * use stroke and strokeWidth * set move: false to only show the tooltip if a hover event occurs * update type for onClose
This commit is contained in:
@@ -57,7 +57,7 @@ export function AppChromeMenu({}: Props) {
|
||||
classNames={animationStyles.overlay}
|
||||
timeout={{ enter: animationSpeed, exit: 0 }}
|
||||
>
|
||||
<FocusScope contain autoFocus>
|
||||
<FocusScope contain autoFocus restoreFocus>
|
||||
<MegaMenu className={styles.menu} onClose={onClose} ref={ref} {...overlayProps} {...dialogProps} />
|
||||
</FocusScope>
|
||||
</CSSTransition>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { autoUpdate, flip, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
|
||||
import React, { useCallback, useId, useMemo, useState } from 'react';
|
||||
import { usePopperTooltip } from 'react-popper-tooltip';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@@ -85,13 +85,19 @@ export function NestedFolderPicker({
|
||||
const rootCollection = useSelector(rootItemsSelector);
|
||||
const childrenCollections = useSelector(childrenByParentUIDSelector);
|
||||
|
||||
const { getTooltipProps, setTooltipRef, setTriggerRef, visible, triggerRef } = usePopperTooltip({
|
||||
visible: overlayOpen,
|
||||
// the order of middleware is important!
|
||||
const middleware = [
|
||||
flip({
|
||||
// see https://floating-ui.com/docs/flip#combining-with-shift
|
||||
crossAxis: false,
|
||||
boundary: document.body,
|
||||
}),
|
||||
];
|
||||
|
||||
const { context, refs, floatingStyles, elements } = useFloating({
|
||||
open: overlayOpen,
|
||||
placement: 'bottom',
|
||||
interactive: true,
|
||||
offset: [0, 0],
|
||||
trigger: 'click',
|
||||
onVisibleChange: (value: boolean) => {
|
||||
onOpenChange: (value) => {
|
||||
// ensure state is clean on opening the overlay
|
||||
if (value) {
|
||||
setSearch('');
|
||||
@@ -99,8 +105,15 @@ export function NestedFolderPicker({
|
||||
}
|
||||
setOverlayOpen(value);
|
||||
},
|
||||
middleware,
|
||||
whileElementsMounted: autoUpdate,
|
||||
});
|
||||
|
||||
const click = useClick(context);
|
||||
const dismiss = useDismiss(context);
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
|
||||
|
||||
const handleFolderExpand = useCallback(
|
||||
async (uid: string, newOpenState: boolean) => {
|
||||
setFolderOpenState((old) => ({ ...old, [uid]: newOpenState }));
|
||||
@@ -209,7 +222,7 @@ export function NestedFolderPicker({
|
||||
handleFolderExpand,
|
||||
idPrefix: overlayId,
|
||||
search,
|
||||
visible,
|
||||
visible: overlayOpen,
|
||||
});
|
||||
|
||||
let label = selectedFolder.data?.title;
|
||||
@@ -217,14 +230,14 @@ export function NestedFolderPicker({
|
||||
label = 'Dashboards';
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
if (!overlayOpen) {
|
||||
return (
|
||||
<Trigger
|
||||
label={label}
|
||||
invalid={invalid}
|
||||
isLoading={selectedFolder.isLoading}
|
||||
autoFocus={autoFocusButton}
|
||||
ref={setTriggerRef}
|
||||
ref={refs.setReference}
|
||||
aria-label={
|
||||
label
|
||||
? t('browse-dashboards.folder-picker.accessible-label', 'Select folder: {{ label }} currently selected', {
|
||||
@@ -232,6 +245,7 @@ export function NestedFolderPicker({
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
{...getReferenceProps()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -239,14 +253,13 @@ export function NestedFolderPicker({
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
ref={setTriggerRef}
|
||||
ref={refs.setReference}
|
||||
autoFocus
|
||||
prefix={label ? <Icon name="folder" /> : null}
|
||||
placeholder={label ?? t('browse-dashboards.folder-picker.search-placeholder', 'Search folders')}
|
||||
value={search}
|
||||
invalid={invalid}
|
||||
className={styles.search}
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
aria-autocomplete="list"
|
||||
aria-expanded
|
||||
@@ -256,16 +269,18 @@ export function NestedFolderPicker({
|
||||
aria-activedescendant={getDOMId(overlayId, flatTree[focusedItemIndex]?.item.uid)}
|
||||
role="combobox"
|
||||
suffix={<Icon name="search" />}
|
||||
{...getReferenceProps()}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<fieldset
|
||||
ref={setTooltipRef}
|
||||
ref={refs.setFloating}
|
||||
id={overlayId}
|
||||
{...getTooltipProps({
|
||||
className: styles.tableWrapper,
|
||||
style: {
|
||||
width: triggerRef?.clientWidth,
|
||||
},
|
||||
})}
|
||||
className={styles.tableWrapper}
|
||||
style={{
|
||||
...floatingStyles,
|
||||
width: elements.domReference?.clientWidth,
|
||||
}}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
{error ? (
|
||||
<Alert
|
||||
|
||||
@@ -33,14 +33,9 @@ function renderTraceViewContainer(frames = [frameOld]) {
|
||||
|
||||
describe('TraceViewContainer', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
// Need to use delay: null here to work with fakeTimers
|
||||
// see https://github.com/testing-library/user-event/issues/833
|
||||
user = userEvent.setup({ delay: null });
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
user = userEvent.setup();
|
||||
});
|
||||
|
||||
it('toggles children visibility', async () => {
|
||||
|
||||
@@ -109,8 +109,9 @@ const addFilter = async (
|
||||
await waitFor(() => expect(screen.getByText(property)).toBeInTheDocument());
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Property')).toBeInTheDocument());
|
||||
const operationSelect = await screen.getAllByText('=');
|
||||
selectOptionInTest(operationSelect[index], operationLabel);
|
||||
const operationSelect = await screen.findAllByText('=');
|
||||
await userEvent.click(operationSelect[index]);
|
||||
await userEvent.click(screen.getByRole('menuitemradio', { name: operationLabel }));
|
||||
await waitFor(() => expect(screen.getByText(operationLabel)).toBeInTheDocument());
|
||||
|
||||
const valueSelect = await screen.findByText('Value');
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { css } from '@emotion/css';
|
||||
import {
|
||||
autoUpdate,
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
useClick,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useInteractions,
|
||||
} from '@floating-ui/react';
|
||||
import React, { useState } from 'react';
|
||||
import { usePopperTooltip } from 'react-popper-tooltip';
|
||||
|
||||
import { GrafanaTheme2, renderMarkdown } from '@grafana/data';
|
||||
import { FlexItem } from '@grafana/experimental';
|
||||
@@ -16,28 +25,46 @@ export interface Props {
|
||||
export const OperationInfoButton = React.memo<Props>(({ def, operation }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const [show, setShow] = useState(false);
|
||||
const { getTooltipProps, setTooltipRef, setTriggerRef, visible } = usePopperTooltip({
|
||||
|
||||
// the order of middleware is important!
|
||||
const middleware = [
|
||||
offset(16),
|
||||
flip({
|
||||
fallbackAxisSideDirection: 'end',
|
||||
// see https://floating-ui.com/docs/flip#combining-with-shift
|
||||
crossAxis: false,
|
||||
boundary: document.body,
|
||||
}),
|
||||
shift(),
|
||||
];
|
||||
|
||||
const { context, refs, floatingStyles } = useFloating({
|
||||
open: show,
|
||||
placement: 'top',
|
||||
visible: show,
|
||||
offset: [0, 16],
|
||||
onVisibleChange: setShow,
|
||||
interactive: true,
|
||||
trigger: ['click'],
|
||||
onOpenChange: setShow,
|
||||
middleware,
|
||||
whileElementsMounted: autoUpdate,
|
||||
});
|
||||
|
||||
const click = useClick(context);
|
||||
const dismiss = useDismiss(context);
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
title="Click to show description"
|
||||
ref={setTriggerRef}
|
||||
ref={refs.setReference}
|
||||
icon="info-circle"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
fill="text"
|
||||
{...getReferenceProps()}
|
||||
/>
|
||||
{visible && (
|
||||
{show && (
|
||||
<Portal>
|
||||
<div ref={setTooltipRef} {...getTooltipProps()} className={styles.docBox}>
|
||||
<div ref={refs.setFloating} style={floatingStyles} {...getFloatingProps()} className={styles.docBox}>
|
||||
<div className={styles.docBoxHeader}>
|
||||
<span>{def.renderer(operation, def, '<expr>')}</span>
|
||||
<FlexItem grow={1} />
|
||||
@@ -86,14 +113,6 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
marginBottom: theme.spacing(-1),
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
signature: css({
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
fontFamily: theme.typography.fontFamilyMonospace,
|
||||
}),
|
||||
dropdown: css({
|
||||
opacity: 0,
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
};
|
||||
};
|
||||
function getOperationDocs(def: QueryBuilderOperationDef, op: QueryBuilderOperation): string {
|
||||
|
||||
Reference in New Issue
Block a user