Panel Header: Add an "untitled" placeholder, and change the dragging icon in the hover header (#64700)

Co-authored-by: Ivan Ortega <ivanortegaalba@gmail.com>
This commit is contained in:
Alexa V 2023-03-15 14:28:52 +01:00 committed by GitHub
parent a996344e14
commit bc5400d8f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 75 additions and 36 deletions

View File

@ -29,6 +29,7 @@ export const availableIconsIndex = {
'arrow-up': true, 'arrow-up': true,
'arrows-h': true, 'arrows-h': true,
'arrows-v': true, 'arrows-v': true,
'expand-arrows': true,
at: true, at: true,
backward: true, backward: true,
bars: true, bars: true,

View File

@ -1,5 +1,4 @@
import { css } from '@emotion/css'; import { css, cx } from '@emotion/css';
import classnames from 'classnames';
import React, { ReactElement, useCallback, useRef, useState } from 'react'; import React, { ReactElement, useCallback, useRef, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
@ -38,18 +37,19 @@ export function HoverWidget({ menu, title, dragClass, children, offset = -32 }:
return ( return (
<div <div
className={classnames(styles.container, { 'show-on-hover': !menuOpen })} className={cx(styles.container, { 'show-on-hover': !menuOpen })}
style={{ top: `${offset}px` }} style={{ top: `${offset}px` }}
data-testid="hover-header-container" data-testid="hover-header-container"
> >
<div <div
className={classnames(styles.square, styles.draggable, dragClass)} className={cx(styles.square, styles.draggable, dragClass)}
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
onPointerUp={onPointerUp} onPointerUp={onPointerUp}
ref={draggableRef} ref={draggableRef}
> >
<Icon name="draggabledots" /> <Icon name="expand-arrows" className={styles.draggableIcon} />
</div> </div>
{!title && <h6 className={cx(styles.untitled, styles.draggable, dragClass)}>Untitled</h6>}
{children} {children}
<div className={styles.square}> <div className={styles.square}>
<PanelMenu <PanelMenu
@ -77,7 +77,7 @@ function getStyles(theme: GrafanaTheme2) {
position: 'absolute', position: 'absolute',
zIndex: 1, zIndex: 1,
right: 0, right: 0,
boxSizing: 'border-box', boxSizing: 'content-box',
alignItems: 'center', alignItems: 'center',
background: theme.colors.background.secondary, background: theme.colors.background.secondary,
color: theme.colors.text.primary, color: theme.colors.text.primary,
@ -92,18 +92,37 @@ function getStyles(theme: GrafanaTheme2) {
alignItems: 'center', alignItems: 'center',
width: theme.spacing(4), width: theme.spacing(4),
height: '100%', height: '100%',
paddingRight: theme.spacing(0.5),
}), }),
draggable: css({ draggable: css({
cursor: 'move', cursor: 'move',
// mobile do not support draggable panels
[theme.breakpoints.down('md')]: {
display: 'none',
},
}), }),
menuButton: css({ menuButton: css({
color: theme.colors.text.primary, // Background and border are overriden when topnav toggle is disabled
'&:hover': {
background: 'inherit', background: 'inherit',
border: 'none',
'&:hover': {
background: theme.colors.secondary.main,
}, },
}), }),
title: css({ title: css({
padding: theme.spacing(0.75), padding: theme.spacing(0.75),
}), }),
untitled: css({
color: theme.colors.text.disabled,
fontStyle: 'italic',
marginBottom: 0,
}),
draggableIcon: css({
transform: 'rotate(45deg)',
color: theme.colors.text.secondary,
'&:hover': {
color: theme.colors.text.primary,
},
}),
}; };
} }

View File

@ -1,5 +1,6 @@
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import React, { CSSProperties, ReactElement, ReactNode } from 'react'; import React, { CSSProperties, ReactElement, ReactNode } from 'react';
import { useMedia } from 'react-use';
import { GrafanaTheme2, LoadingState } from '@grafana/data'; import { GrafanaTheme2, LoadingState } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
@ -84,7 +85,14 @@ export function PanelChrome({
}: PanelChromeProps) { }: PanelChromeProps) {
const theme = useTheme2(); const theme = useTheme2();
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const hasHeader = !hoverHeader;
const pointerQuery = '(pointer: coarse)';
// detect if we are on touch devices
const isTouchDevice = useMedia(pointerQuery);
const hasHeader = !hoverHeader || isTouchDevice;
// hover menu is only shown on hover when not on touch devices
const showOnHoverClass = !isTouchDevice ? 'show-on-hover' : '';
const headerHeight = getHeaderHeight(theme, hasHeader); const headerHeight = getHeaderHeight(theme, hasHeader);
const { contentStyle, innerWidth, innerHeight } = getContentStyle(padding, theme, width, headerHeight, height); const { contentStyle, innerWidth, innerHeight } = getContentStyle(padding, theme, width, headerHeight, height);
@ -131,7 +139,7 @@ export function PanelChrome({
{loadingState === LoadingState.Loading ? <LoadingBar width={width} ariaLabel="Panel loading bar" /> : null} {loadingState === LoadingState.Loading ? <LoadingBar width={width} ariaLabel="Panel loading bar" /> : null}
</div> </div>
{hoverHeader && ( {hoverHeader && !isTouchDevice && (
<> <>
{menu && ( {menu && (
<HoverWidget menu={menu} title={title} offset={hoverHeaderOffset} dragClass={dragClass}> <HoverWidget menu={menu} title={title} offset={hoverHeaderOffset} dragClass={dragClass}>
@ -162,7 +170,12 @@ export function PanelChrome({
menu={menu} menu={menu}
title={title} title={title}
placement="bottom-end" placement="bottom-end"
menuButtonClass={cx(styles.menuItem, dragClassCancel, 'show-on-hover')} menuButtonClass={cx(
{ [styles.hiddenMenu]: !isTouchDevice },
styles.menuItem,
dragClassCancel,
showOnHoverClass
)}
/> />
)} )}
@ -296,9 +309,11 @@ const getStyles = (theme: GrafanaTheme2) => {
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
}), }),
hiddenMenu: css({
visibility: 'hidden',
}),
menuItem: css({ menuItem: css({
label: 'panel-menu', label: 'panel-menu',
visibility: 'hidden',
border: 'none', border: 'none',
background: theme.colors.secondary.main, background: theme.colors.secondary.main,
'&:hover': { '&:hover': {

View File

@ -27,7 +27,7 @@ export const TitleItem = forwardRef<HTMLAnchorElement, TitleItemProps>(
onClick={onClick} onClick={onClick}
target={target} target={target}
title={title} title={title}
className={cx(styles.item, className)} className={cx(styles.linkItem, className)}
{...rest} {...rest}
> >
{children} {children}
@ -46,8 +46,7 @@ export const TitleItem = forwardRef<HTMLAnchorElement, TitleItemProps>(
TitleItem.displayName = 'TitleItem'; TitleItem.displayName = 'TitleItem';
const getStyles = (theme: GrafanaTheme2) => { const getStyles = (theme: GrafanaTheme2) => {
return { const item = css({
item: css({
color: `${theme.colors.text.secondary}`, color: `${theme.colors.text.secondary}`,
label: 'panel-header-item', label: 'panel-header-item',
cursor: 'auto', cursor: 'auto',
@ -68,7 +67,12 @@ const getStyles = (theme: GrafanaTheme2) => {
'&:hover ': { '&:hover ': {
boxShadow: `${theme.shadows.z1}`, boxShadow: `${theme.shadows.z1}`,
background: `${theme.colors.background.secondary}`, background: `${theme.colors.background.secondary}`,
color: `${theme.colors.text.primary}`,
}, },
}), });
return {
item,
linkItem: cx(item, css({ cursor: 'pointer' })),
}; };
}; };