mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ElementSelection: New element selection context to support selecting elements (#97029)
* ElementSelection: New element selction context to support selecting elements like panels * Update * Update
This commit is contained in:
parent
6974f77d18
commit
335241d93d
@ -3762,7 +3762,8 @@ exports[`better eslint`] = {
|
||||
"public/app/features/sandbox/TestStuffPage.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"]
|
||||
],
|
||||
"public/app/features/scopes/index.ts:5381": [
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./instance\`)", "0"],
|
||||
|
@ -0,0 +1,52 @@
|
||||
import React, { createContext, useCallback, useContext } from 'react';
|
||||
|
||||
/** @alpha */
|
||||
export interface ElementSelectionContextState {
|
||||
/**
|
||||
* Turn on selection mode & show selection state
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/** List of currently selected elements */
|
||||
selected: ElementSelectionContextItem[];
|
||||
onSelect: (item: ElementSelectionContextItem, multi?: boolean) => void;
|
||||
}
|
||||
|
||||
export interface ElementSelectionContextItem {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const ElementSelectionContext = createContext<ElementSelectionContextState | undefined>(undefined);
|
||||
|
||||
export interface UseElementSelectionResult {
|
||||
isSelected?: boolean;
|
||||
isSelectable?: boolean;
|
||||
onSelect?: (evt: React.PointerEvent) => void;
|
||||
}
|
||||
|
||||
export function useElementSelection(id: string | undefined): UseElementSelectionResult {
|
||||
if (!id) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const context = useContext(ElementSelectionContext);
|
||||
if (!context) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const isSelected = context.selected.some((item) => item.id === id);
|
||||
const onSelect = useCallback<React.PointerEventHandler>(
|
||||
(evt) => {
|
||||
if (!context.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// To prevent this click form clearing the selection
|
||||
evt.stopPropagation();
|
||||
|
||||
context.onSelect({ id }, evt.shiftKey);
|
||||
},
|
||||
[context, id]
|
||||
);
|
||||
|
||||
return { isSelected, onSelect, isSelectable: context.enabled };
|
||||
}
|
@ -9,6 +9,7 @@ import { selectors } from '@grafana/e2e-selectors';
|
||||
import { useStyles2, useTheme2 } from '../../themes';
|
||||
import { getFocusStyles } from '../../themes/mixins';
|
||||
import { DelayRender } from '../../utils/DelayRender';
|
||||
import { useElementSelection } from '../ElementSelectionContext/ElementSelectionContext';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { LoadingBar } from '../LoadingBar/LoadingBar';
|
||||
import { Text } from '../Text/Text';
|
||||
@ -33,6 +34,7 @@ interface BaseProps {
|
||||
menu?: ReactElement | (() => ReactElement);
|
||||
dragClass?: string;
|
||||
dragClassCancel?: string;
|
||||
selectionId?: string;
|
||||
/**
|
||||
* Use only to indicate loading or streaming data in the panel.
|
||||
* Any other values of loadingState are ignored.
|
||||
@ -131,6 +133,7 @@ export function PanelChrome({
|
||||
statusMessageOnClick,
|
||||
leftItems,
|
||||
actions,
|
||||
selectionId,
|
||||
onCancelQuery,
|
||||
onOpenMenu,
|
||||
collapsible = false,
|
||||
@ -145,6 +148,7 @@ export function PanelChrome({
|
||||
const styles = useStyles2(getStyles);
|
||||
const panelContentId = useId();
|
||||
const panelTitleId = useId().replace(/:/g, '_');
|
||||
const { isSelected, onSelect } = useElementSelection(selectionId);
|
||||
|
||||
const hasHeader = !hoverHeader;
|
||||
|
||||
@ -263,7 +267,11 @@ export function PanelChrome({
|
||||
return (
|
||||
// tabIndex={0} is needed for keyboard accessibility in the plot area
|
||||
<section
|
||||
className={cx(styles.container, { [styles.transparentContainer]: isPanelTransparent })}
|
||||
className={cx(
|
||||
styles.container,
|
||||
isPanelTransparent && styles.transparentContainer,
|
||||
isSelected && 'dashboard-selected-element'
|
||||
)}
|
||||
style={containerStyles}
|
||||
aria-labelledby={!!title ? panelTitleId : undefined}
|
||||
data-testid={testid}
|
||||
@ -300,7 +308,12 @@ export function PanelChrome({
|
||||
)}
|
||||
|
||||
{hasHeader && (
|
||||
<div className={cx(styles.headerContainer, dragClass)} style={headerStyles} data-testid="header-container">
|
||||
<div
|
||||
className={cx(styles.headerContainer, dragClass)}
|
||||
style={headerStyles}
|
||||
data-testid="header-container"
|
||||
onPointerUp={onSelect}
|
||||
>
|
||||
{statusMessage && (
|
||||
<div className={dragClassCancel}>
|
||||
<PanelStatus message={statusMessage} onClick={statusMessageOnClick} ariaLabel="Panel status" />
|
||||
|
@ -322,3 +322,9 @@ export { type GraphNGLegendEvent } from '../graveyard/GraphNG/types';
|
||||
|
||||
export { ZoomPlugin } from '../graveyard/uPlot/plugins/ZoomPlugin';
|
||||
export { TooltipPlugin } from '../graveyard/uPlot/plugins/TooltipPlugin';
|
||||
|
||||
export {
|
||||
ElementSelectionContext,
|
||||
useElementSelection,
|
||||
type ElementSelectionContextState,
|
||||
} from './ElementSelectionContext/ElementSelectionContext';
|
||||
|
@ -67,5 +67,11 @@ export function getDashboardGridStyles(theme: GrafanaTheme2) {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'.dashboard-selected-element': {
|
||||
outline: `2px dashed ${theme.colors.primary.border}`,
|
||||
outlineOffset: '0px',
|
||||
borderRadius: '2px',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NavModelItem } from '@grafana/data';
|
||||
import { getPluginExtensions, isPluginExtensionLink } from '@grafana/runtime';
|
||||
import { Button, LinkButton, Stack } from '@grafana/ui';
|
||||
import { Button, LinkButton, Stack, Text } from '@grafana/ui';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
|
||||
@ -17,8 +17,9 @@ export const TestStuffPage = () => {
|
||||
|
||||
return (
|
||||
<Page navModel={{ node: node, main: node }}>
|
||||
<LinkToBasicApp extensionPointId="grafana/sandbox/testing" />
|
||||
<Text variant="h5">Application notifications (toasts) testing</Text>
|
||||
<Stack>
|
||||
<LinkToBasicApp extensionPointId="grafana/sandbox/testing" />
|
||||
<Button onClick={() => notifyApp.success('Success toast', 'some more text goes here')} variant="primary">
|
||||
Success
|
||||
</Button>
|
||||
|
Loading…
Reference in New Issue
Block a user