mirror of https://github.com/grafana/grafana.git
ButtonSelect: Fixes menu shadow (fixes issue with RefreshPicker) (#111431)
* ButtonSelect: Fixes menu shadow * Update e2e tests to look in portal
This commit is contained in:
parent
bd550d2f06
commit
28c19036f1
|
@ -3,13 +3,14 @@ import { test, expect } from '@grafana/plugin-e2e';
|
|||
import pluginJson from '../plugin.json';
|
||||
import testApp3pluginJson from '../plugins/grafana-extensionexample3-app/plugin.json';
|
||||
import { testIds } from '../testIds';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
test.describe('grafana-extensionstest-app', { tag: ['@plugins'] }, () => {
|
||||
test('should extend the actions menu with a link to a-app plugin', async ({ page }) => {
|
||||
await page.goto(`/a/${pluginJson.id}/added-links`);
|
||||
const section = await page.getByTestId(testIds.addedLinksPage.section1);
|
||||
await section.getByTestId(testIds.actions.button).click();
|
||||
await page.getByTestId(testIds.container).getByText('Go to A').click();
|
||||
await page.getByTestId(selectors.components.Portal.container).getByText('Go to A').click();
|
||||
await page.getByTestId(testIds.modal.open).click();
|
||||
await expect(page.getByTestId(testIds.appA.container)).toBeVisible();
|
||||
});
|
||||
|
@ -18,7 +19,7 @@ test.describe('grafana-extensionstest-app', { tag: ['@plugins'] }, () => {
|
|||
await page.goto(`/a/${pluginJson.id}/added-links`);
|
||||
const section = await page.getByTestId(testIds.addedLinksPage.section1);
|
||||
await section.getByTestId(testIds.actions.button).click();
|
||||
await page.getByTestId(testIds.container).getByText('Open from B').click();
|
||||
await page.getByTestId(selectors.components.Portal.container).getByText('Open from B').click();
|
||||
await expect(page.getByTestId(testIds.appB.modal)).toBeVisible();
|
||||
});
|
||||
|
||||
|
@ -26,7 +27,7 @@ test.describe('grafana-extensionstest-app', { tag: ['@plugins'] }, () => {
|
|||
await page.goto(`/a/${pluginJson.id}/added-links`);
|
||||
const section = await page.getByTestId(testIds.addedLinksPage.section1);
|
||||
await section.getByTestId(testIds.actions.button).click();
|
||||
await page.getByTestId(testIds.container).getByText('Basic link').click();
|
||||
await page.getByTestId(selectors.components.Portal.container).getByText('Basic link').click();
|
||||
await page.getByTestId(testIds.modal.open).click();
|
||||
await expect(page.getByTestId(testIds.appA.container)).toBeVisible();
|
||||
});
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import { css } from '@emotion/css';
|
||||
import { autoUpdate, offset, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react';
|
||||
import { FocusScope } from '@react-aria/focus';
|
||||
import { memo, HTMLAttributes, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes/ThemeContext';
|
||||
import { getPositioningMiddleware } from '../../utils/floating';
|
||||
import { Menu } from '../Menu/Menu';
|
||||
import { MenuItem } from '../Menu/MenuItem';
|
||||
import { ScrollContainer } from '../ScrollContainer/ScrollContainer';
|
||||
import { ToolbarButton, ToolbarButtonVariant } from '../ToolbarButton/ToolbarButton';
|
||||
import { PopoverContent } from '../Tooltip/types';
|
||||
|
||||
import { Dropdown } from './Dropdown';
|
||||
|
||||
export interface Props<T> extends HTMLAttributes<HTMLButtonElement> {
|
||||
className?: string;
|
||||
options: Array<SelectableValue<T>>;
|
||||
|
@ -29,57 +27,16 @@ export interface Props<T> extends HTMLAttributes<HTMLButtonElement> {
|
|||
*/
|
||||
const ButtonSelectComponent = <T,>(props: Props<T>) => {
|
||||
const { className, options, value, onChange, narrow, variant, ...restProps } = props;
|
||||
const styles = useStyles2(getStyles);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const placement = 'bottom-end';
|
||||
|
||||
// the order of middleware is important!
|
||||
const middleware = [offset(0), ...getPositioningMiddleware(placement)];
|
||||
|
||||
const { context, refs, floatingStyles } = useFloating({
|
||||
open: isOpen,
|
||||
placement,
|
||||
onOpenChange: setIsOpen,
|
||||
middleware,
|
||||
whileElementsMounted: autoUpdate,
|
||||
});
|
||||
|
||||
const click = useClick(context);
|
||||
const dismiss = useDismiss(context);
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]);
|
||||
|
||||
const onChangeInternal = (item: SelectableValue<T>) => {
|
||||
onChange(item);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<ToolbarButton
|
||||
className={className}
|
||||
isOpen={isOpen}
|
||||
narrow={narrow}
|
||||
variant={variant}
|
||||
ref={refs.setReference}
|
||||
{...getReferenceProps()}
|
||||
{...restProps}
|
||||
>
|
||||
{value?.label || (value?.value != null ? String(value?.value) : null)}
|
||||
</ToolbarButton>
|
||||
{isOpen && (
|
||||
<div className={styles.menuWrapper} ref={refs.setFloating} {...getFloatingProps()} style={floatingStyles}>
|
||||
<FocusScope contain autoFocus restoreFocus>
|
||||
{/*
|
||||
tabIndex=-1 is needed here to support highlighting text within the menu when using FocusScope
|
||||
see https://github.com/adobe/react-spectrum/issues/1604#issuecomment-781574668
|
||||
*/}
|
||||
const renderMenu = () => (
|
||||
<Menu tabIndex={-1} onClose={() => setIsOpen(false)}>
|
||||
<ScrollContainer maxHeight="100vh">
|
||||
{options.map((item) => (
|
||||
<MenuItem
|
||||
key={`${item.value}`}
|
||||
label={item.label ?? String(item.value)}
|
||||
onClick={() => onChangeInternal(item)}
|
||||
onClick={() => onChange(item)}
|
||||
active={item.value === value?.value}
|
||||
ariaChecked={item.value === value?.value}
|
||||
ariaLabel={item.ariaLabel || item.label}
|
||||
|
@ -88,11 +45,16 @@ const ButtonSelectComponent = <T,>(props: Props<T>) => {
|
|||
role="menuitemradio"
|
||||
/>
|
||||
))}
|
||||
</ScrollContainer>
|
||||
</Menu>
|
||||
</FocusScope>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={renderMenu} placement="bottom-end">
|
||||
<ToolbarButton className={className} isOpen={isOpen} narrow={narrow} variant={variant} {...restProps}>
|
||||
{value?.label || (value?.value != null ? String(value?.value) : null)}
|
||||
</ToolbarButton>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -102,17 +64,3 @@ ButtonSelectComponent.displayName = 'ButtonSelect';
|
|||
// see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/37087#issuecomment-656596623
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
export const ButtonSelect = memo(ButtonSelectComponent) as typeof ButtonSelectComponent;
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
wrapper: css({
|
||||
position: 'relative',
|
||||
display: 'inline-flex',
|
||||
}),
|
||||
menuWrapper: css({
|
||||
zIndex: theme.zIndex.dropdown,
|
||||
maxHeight: '100vh',
|
||||
overflow: 'auto',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue